Improve user experience

master
John Montagu, the 4th Earl of Sandvich 2024-11-17 15:36:46 -08:00
parent bf45b64135
commit abc456b7d1
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
15 changed files with 221 additions and 144 deletions

View File

@ -10,16 +10,17 @@
"dependencies": {
"@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47",
"@programic/vue3-tooltip": "^1.0.0",
"axios": "^1.7.7",
"bootstrap-icons": "^1.11.3",
"css.gg": "^2.1.4",
"moment": "^2.30.1",
"moment-timezone": "^0.5.46",
"pinia": "^2.2.4",
"v-tooltip": "^2.1.3",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"vue-select": "^4.0.0-beta.6"
"vue-select": "^4.0.0-beta.6",
"vue3-tooltip": "^2.2.4"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
@ -112,18 +113,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/runtime": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz",
"integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz",
@ -772,6 +761,31 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
"integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.12",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
"integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.8"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
"license": "MIT"
},
"node_modules/@humanfs/core": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz",
@ -982,6 +996,29 @@
"url": "https://opencollective.com/unts"
}
},
"node_modules/@programic/vue3-tooltip": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@programic/vue3-tooltip/-/vue3-tooltip-1.0.0.tgz",
"integrity": "sha512-KNg0WzMISQuR7cTuuxPgcwEuyYF3dULIMCzt8P/LnOpJv0hM77lxo0u6TPM7BlnoYrYdcOotfcSoE1KJlu5BwQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.0.2",
"uuid": "^9.0.0"
}
},
"node_modules/@programic/vue3-tooltip/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
@ -4946,6 +4983,7 @@
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": {
@ -5820,17 +5858,6 @@
"node": ">= 6"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/postcss": {
"version": "8.4.47",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
@ -6177,12 +6204,6 @@
"node": ">=8.10.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/request-progress": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@ -6507,6 +6528,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -7190,73 +7212,6 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v-tooltip": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
"integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"lodash": "^4.17.21",
"popper.js": "^1.16.1",
"vue-resize": "^1.0.1"
}
},
"node_modules/v-tooltip/node_modules/@vue/compiler-sfc": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
"peer": true,
"dependencies": {
"@babel/parser": "^7.23.5",
"postcss": "^8.4.14",
"source-map": "^0.6.1"
},
"optionalDependencies": {
"prettier": "^1.18.2 || ^2.0.0"
}
},
"node_modules/v-tooltip/node_modules/prettier": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/v-tooltip/node_modules/vue": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"license": "MIT",
"peer": true,
"dependencies": {
"@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0"
}
},
"node_modules/v-tooltip/node_modules/vue-resize": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
"integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"vue": "^2.6.0"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@ -7556,6 +7511,15 @@
"typescript": ">=5.0.0"
}
},
"node_modules/vue3-tooltip": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/vue3-tooltip/-/vue3-tooltip-2.2.4.tgz",
"integrity": "sha512-oQPDBduZpELNg8I3pJR2+zztI6pfIstaVGO+3uKu0RwJlQJ0FqApbggKOFLy/LW6g2Yjcaau22taxAt6ErxuSw==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.47"
}
},
"node_modules/w3c-xmlserializer": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",

View File

@ -17,16 +17,17 @@
"dependencies": {
"@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47",
"@programic/vue3-tooltip": "^1.0.0",
"axios": "^1.7.7",
"bootstrap-icons": "^1.11.3",
"css.gg": "^2.1.4",
"moment": "^2.30.1",
"moment-timezone": "^0.5.46",
"pinia": "^2.2.4",
"v-tooltip": "^2.1.3",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"vue-select": "^4.0.0-beta.6"
"vue-select": "^4.0.0-beta.6",
"vue3-tooltip": "^2.2.4"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",

View File

@ -1,7 +1,10 @@
<script setup lang="ts">
import { RouterLink, RouterView } from "vue-router";
import { useAuthStore } from "./stores/auth";
const baseUrl = window.location.origin;
const authStore = useAuthStore();
</script>
<template>
@ -11,7 +14,14 @@ const baseUrl = window.location.origin;
<h1>availabili.tf</h1>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/schedule">Schedule</RouterLink>
<form action="https://steamcommunity.com/openid/login" method="get">
<div v-if="authStore.isLoggedIn">
Welcome {{ authStore.username }}
</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"

View File

@ -27,12 +27,13 @@ a.button {
button {
display: flex;
align-items: center;
gap: 4px;
/*font-weight: 700;*/
color: var(--text);
background-color: var(--crust);
border: none;
padding: 10px 20px;
padding: 6px 20px;
line-height: 1.6;
border-radius: 4px;
font-family:
Inter,
@ -51,8 +52,22 @@ button {
transition-duration: 200ms;
}
button.icon {
background-color: transparent;
padding: 8px;
}
button.icon:hover {
background-color: var(--surface-0);
border-radius: 4px;
}
button.icon i.bi {
transform: translateY(-0.5px);
}
button > i.bi {
transform: translateY(1px);
/*transform: translateY(1px);*/
}
button > i.bi.margin {
@ -131,7 +146,7 @@ details > summary {
}
details.accordion {
padding: 16px;
padding: 20px;
background-color: var(--mantle);
border: 1px solid var(--mantle);
border-radius: 8px;
@ -237,3 +252,8 @@ input {
display: flex;
justify-content: end;
}
hr {
border: none;
border-top: 1px solid var(--surface-0);
}

View File

@ -39,22 +39,6 @@ function leaveTeam() {
{{ availableMembersNextHour?.length }} available in the next hour
</em>
<div class="team-details-button-group">
<RouterLink class="button" :to="'/schedule?teamId=' + team.id">
<button>
<i class="bi bi-calendar-fill"></i>
</button>
</RouterLink>
<RouterLink class="button" :to="{ name: 'team-settings/' }">
<button>
<i class="bi bi-gear-fill"></i>
</button>
</RouterLink>
<button
class="destructive"
@click="leaveTeam"
>
Leave
</button>
</div>
</div>
<table class="member-table">

View File

@ -7,6 +7,7 @@ import { type ViewTeamMembersResponse, type TeamSchema, RoleSchema } from "@/cli
import SvgIcon from "@jamescoyle/vue-icon";
import { mdiCrown } from "@mdi/js";
import RoleTag from "../components/RoleTag.vue";
import moment from "moment";
const props = defineProps({
player: {
@ -83,6 +84,42 @@ const isUnavailable = computed(() => {
return props.player?.availability[0] == 0 &&
props.player?.availability[1] == 0;
});
const nextHour = computed(() => {
const now = moment().utc();
const time = now.clone().tz(props.team.tzTimezone);
if (time.minute() >= props.team.minuteOffset) {
time.add(1, "hour");
time.minute(props.team.minuteOffset);
}
const diff = time.utc().diff(now, "minutes", false);
return `${diff} minute(s) (${time.local().format("LT")})`;
});
const leftIndicator = computed(() => {
switch (props.player?.availability[0]) {
case 0:
return "Not currently available";
case 1:
return "Currently available if needed";
case 2:
return "Currently available";
}
});
const rightIndicator = computed(() => {
switch (props.player?.availability[1]) {
case 0:
return `Not available in ${nextHour.value}`;
case 1:
return `Available if needed in ${nextHour.value}`;
case 2:
return `Available in ${nextHour.value}`;
}
});
</script>
<template>
@ -95,10 +132,12 @@ const isUnavailable = computed(() => {
<div class="status-indicators">
<span
class="indicator left-indicator"
v-tooltip="leftIndicator"
:availability="player.availability[0]"
/>
<span
class="indicator right-indicator"
v-tooltip="rightIndicator"
:availability="player.availability[1]"
/>
</div>
@ -118,15 +157,23 @@ const isUnavailable = computed(() => {
v-model="roles[i]"
/>
</div>
<template v-else>
<i
<template v-else-if="player.roles.length > 0">
<div
class="role-icon"
v-for="role in player.roles"
:class="{
[rosterStore.roleIcons[role.role]]: true,
main: role.isMain,
}"
/>
v-tooltip="rosterStore.roleNames[role.role]"
>
<i
:class="{
[rosterStore.roleIcons[role.role]]: true,
main: role.isMain,
}"
/>
</div>
</template>
<span v-else>
No roles
</span>
</div>
</td>
<td>
@ -209,7 +256,7 @@ const isUnavailable = computed(() => {
.role-icons i {
font-size: 24px;
line-height: 0;
line-height: 1;
color: var(--overlay-0);
}

View File

@ -4,14 +4,17 @@ import "vue-select/dist/vue-select.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import VueSelect from "vue-select";
import { TooltipDirective } from "vue3-tooltip";
import "vue3-tooltip/tooltip.css";
import App from "./App.vue";
import router from "./router";
const app = createApp(App)
const app = createApp(App);
app.use(createPinia())
app.use(router)
app.use(createPinia());
app.use(router);
app.directive("tooltip", TooltipDirective);
app.component("v-select", VueSelect);
app.mount("#app")
app.mount("#app");

View File

@ -31,6 +31,8 @@ export const useScheduleStore = defineStore("schedule", () => {
const overlay: Ref<AvailabilitySchema[] | undefined> = ref();
const selectedMembers = ref<AvailabilitySchema[]>();
const hoveredIndex: Ref<number | undefined> = ref();
const team = ref();
@ -113,6 +115,7 @@ export const useScheduleStore = defineStore("schedule", () => {
availability,
playerAvailability,
overlay,
selectedMembers,
hoveredIndex,
fetchSchedule,
fetchTeamSchedule,

View File

@ -4,6 +4,7 @@ import { defineStore } from "pinia";
import { computed, reactive, ref, type Reactive, type Ref } from "vue";
import { useClientStore } from "./client";
import { useAuthStore } from "./auth";
import moment from "moment";
export type TeamMap = { [id: number]: TeamSchema };

View File

@ -7,13 +7,14 @@ import { computed, onMounted, reactive, ref, watch } from "vue";
import { useTeamsStore } from "../stores/teams";
import { useScheduleStore } from "../stores/schedule";
import { useRoute, useRouter } from "vue-router";
import type { TeamSchema } from "@/client";
const teamsStore = useTeamsStore();
const schedule = useScheduleStore();
const router = useRouter();
const route = useRoute();
const options = ref([ ]);
const options = ref<TeamSchema[]>([ ]);
const firstHour = computed(() => shouldShowAllHours.value ? 0 : 14);
const lastHour = computed(() => shouldShowAllHours.value ? 23 : 22);

View File

@ -45,11 +45,18 @@ onMounted(() => {
<span class="aside">
Formed on {{ creationDate }}
</span>
</center>
<center class="margin">
<RouterLink :to="{ name: 'team-settings' }">
Settings
</RouterLink>
<div class="icons">
<RouterLink class="button" :to="'/schedule?teamId=' + team.id">
<button class="icon" v-tooltip="'Schedule'">
<i class="bi bi-calendar-fill"></i>
</button>
</RouterLink>
<RouterLink class="button" :to="{ name: 'team-settings/' }">
<button class="icon" v-tooltip="'Settings'">
<i class="bi bi-gear-fill"></i>
</button>
</RouterLink>
</div>
</center>
<MembersList />
</template>
@ -60,4 +67,24 @@ onMounted(() => {
.margin {
margin: 4em;
}
.icons {
display: flex;
justify-content: center;
margin: 8px;
gap: 4px;
}
.icons a {
border-radius: 4px;
}
.icons button {
color: var(--overlay-0);
font-size: 12pt;
}
.icons button:hover {
color: var(--text);
}
</style>

View File

@ -11,6 +11,7 @@ const {
<template>
<div class="team-general-settings">
<h2>Overview</h2>
<div>
<div class="form-group margin">
<h3 class="closer">Team Name</h3>

View File

@ -3,6 +3,18 @@
<template>
<div class="team-integrations">
This team currently does not have any integrations.
<h2>Team Integrations</h2>
<div v-if="true">
This team currently does not have any integrations.
</div>
<div v-else>
<details class="accordion">
<summary>
<h2>Discord Webhook</h2>
</summary>
<h3>Webhook URL</h3>
<input hidden />
</details>
</div>
</div>
</template>

View File

@ -24,6 +24,7 @@ onMounted(() => {
<template>
<div class="invites" v-if="team">
<h2>Invites</h2>
<table id="invite-table" v-if="invites?.length > 0">
<thead>
<tr>

View File

@ -36,6 +36,7 @@ onMounted(() => {
<RouterLink class="tab" :to="{ name: 'team-settings/invites' }">
Invites
</RouterLink>
<hr>
</div>
</nav>
<div class="view">
@ -67,20 +68,21 @@ onMounted(() => {
nav.sidebar h3 {
text-transform: uppercase;
color: var(--overlay-0);
padding: 0.5em 16px;
padding: 0 8px;
font-size: 8pt;
}
nav.sidebar > .categories {
display: flex;
flex-direction: column;
width: 256px;
width: 192px;
gap: 4px;
}
nav.sidebar a.tab {
font-size: 12pt;
font-size: 11pt;
color: var(--overlay-0);
padding: 8px 16px;
padding: 6px 10px;
font-weight: 500;
border-radius: 4px;
}