Improve code quality

master
John Montagu, the 4th Earl of Sandvich 2024-11-15 23:19:48 -08:00
parent cb9e29b402
commit b4deeddfba
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
11 changed files with 293 additions and 215 deletions

View File

@ -31,6 +31,7 @@
"@vue/eslint-config-typescript": "^14.0.1",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"@vue/typescript-plugin": "^2.1.10",
"autoprefixer": "^10.4.20",
"cypress": "^13.15.0",
"eslint": "^9.12.0",
@ -1620,30 +1621,30 @@
}
},
"node_modules/@volar/language-core": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.6.tgz",
"integrity": "sha512-FxUfxaB8sCqvY46YjyAAV6c3mMIq/NWQMVvJ+uS4yxr1KzOvyg61gAuOnNvgCvO4TZ7HcLExBEsWcDu4+K4E8A==",
"version": "2.4.10",
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz",
"integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.6"
"@volar/source-map": "2.4.10"
}
},
"node_modules/@volar/source-map": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.6.tgz",
"integrity": "sha512-Nsh7UW2ruK+uURIPzjJgF0YRGP5CX9nQHypA2OMqdM2FKy7rh+uv3XgPnWPw30JADbKvZ5HuBzG4gSbVDYVtiw==",
"version": "2.4.10",
"resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz",
"integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==",
"dev": true,
"license": "MIT"
},
"node_modules/@volar/typescript": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.6.tgz",
"integrity": "sha512-NMIrA7y5OOqddL9VtngPWYmdQU03htNKFtAYidbYfWA0TOhyGVd9tfcP4TsLWQ+RBWDZCbBqsr8xzU0ZOxYTCQ==",
"version": "2.4.10",
"resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz",
"integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.6",
"@volar/language-core": "2.4.10",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
@ -1861,6 +1862,43 @@
"dev": true,
"license": "MIT"
},
"node_modules/@vue/typescript-plugin": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-2.1.10.tgz",
"integrity": "sha512-NrS3BB3l5vuZHU4Vp8l9TbT5pC7VjBfwZKqc24dAXF3Z+dJyGs4mcC3zo59gUggLMQSah8mdXj8xqEfMkrps8w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/typescript": "~2.4.8",
"@vue/language-core": "2.1.10",
"@vue/shared": "^3.5.0"
}
},
"node_modules/@vue/typescript-plugin/node_modules/@vue/language-core": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz",
"integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@volar/language-core": "~2.4.8",
"@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0",
"alien-signals": "^0.2.0",
"minimatch": "^9.0.3",
"muggle-string": "^0.4.1",
"path-browserify": "^1.0.1"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
@ -1938,6 +1976,13 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/alien-signals": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz",
"integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==",
"dev": true,
"license": "MIT"
},
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",

View File

@ -38,6 +38,7 @@
"@vue/eslint-config-typescript": "^14.0.1",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"@vue/typescript-plugin": "^2.1.10",
"autoprefixer": "^10.4.20",
"cypress": "^13.15.0",
"eslint": "^9.12.0",

View File

@ -1,12 +1,15 @@
<script setup lang="ts">
import { type TeamInviteSchema } from "../client";
import { useTeamsStore } from "../stores/teams";
import { computed } from "vue";
import { computed, type PropType } from "vue";
const teamsStore = useTeamsStore();
const props = defineProps({
invite: Object as PropType<TeamInviteSchema>,
invite: {
type: Object as PropType<TeamInviteSchema>,
required: true,
},
});
const inviteLink = computed(() => {

View File

@ -0,0 +1,41 @@
import { useTeamsStore } from "@/stores/teams";
import { computed } from "vue";
import { useRoute } from "vue-router";
export function useTeamDetails() {
const route = useRoute();
const teamsStore = useTeamsStore();
const teamId = computed(() => Number(route.params.id));
const team = computed(() => {
return teamsStore.teams[teamId.value];
});
const invites = computed(() => {
return teamsStore.teamInvites[teamId.value];
});
const teamMembers = computed(() => {
return teamsStore.teamMembers[teamId.value];
});
const availableMembers = computed(() => {
return teamsStore.teamMembers[teamId.value]
.filter((member) => member.availability[0] > 0);
});
const availableMembersNextHour = computed(() => {
return teamsStore.teamMembers[teamId.value]
.filter((member) => member.availability[1] > 0);
});
return {
team,
teamId,
invites,
teamMembers,
availableMembers,
availableMembersNextHour,
}
}

View File

@ -0,0 +1,10 @@
import { ref } from "vue";
export function useTeamSettings() {
const teamName = ref("");
const timezone = ref(
Intl.DateTimeFormat().resolvedOptions().timeZone ??
"Etc/UTC"
);
}

View File

@ -6,6 +6,7 @@ import LoginView from "../views/LoginView.vue";
import TeamRegistrationView from "../views/TeamRegistrationView.vue";
import TeamDetailsView from "../views/TeamDetailsView.vue";
import { useAuthStore } from "@/stores/auth";
import TeamDetailsMembersListView from "../views/TeamDetailsMembersListView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -39,10 +40,16 @@ const router = createRouter({
path: "/team/id/:id",
name: "team-details",
component: TeamDetailsView,
//children: [
// path: "members",
// component:
//],
children: [
{
path: "",
component: TeamDetailsMembersListView,
},
{
path: "",
component: TeamDetailsMembersListView,
},
],
},
]
});

View File

@ -1,6 +1,7 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useClientStore } from "./client";
import type { LocationQuery } from "vue-router";
export const useAuthStore = defineStore("auth", () => {
const clientStore = useClientStore();
@ -25,7 +26,7 @@ export const useAuthStore = defineStore("auth", () => {
);
}
async function login(queryParams: { [key: string]: string }) {
async function login(queryParams: LocationQuery) {
return fetch(import.meta.env.VITE_API_BASE_URL + "/login/authenticate", {
headers: {
"Content-Type": "application/json",

View File

@ -12,13 +12,6 @@ const auth = useAuthStore();
const registerUsername = ref("");
function register() {
//const params = {
// ...queryParams.value,
// username: registerUsername.value,
//};
//auth.login(params)
// .then(() => router.push("/"));
auth.setUsername(registerUsername.value)
.then(() => router.push("/"));
}
@ -39,8 +32,8 @@ onMounted(() => {
<template v-if="auth.isRegistering">
<h1>New account</h1>
<p>
Your account has been newly created. Select a username to be
associated with this account.
Your account has been created with your username set to your Steam ID
by default. Select a new username to be associated with this account.
</p>
<div class="form-group margin">
<h3>Username</h3>

View File

@ -0,0 +1,153 @@
<script setup lang="ts">
import { useTeamsStore } from "../stores/teams";
import { useRoute, useRouter, RouterLink } from "vue-router";
import { computed } from "vue";
import { useTeamDetails } from "../composables/team-details";
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
import InviteEntry from "../components/InviteEntry.vue";
const route = useRoute();
const router = useRouter();
const teamsStore = useTeamsStore();
const {
team,
invites,
availableMembers,
availableMembersNextHour,
teamMembers,
} = useTeamDetails();
function createInvite() {
teamsStore.createInvite(team.value.id);
}
function leaveTeam() {
teamsStore.leaveTeam(team.value.id)
.then(() => {
teamsStore.fetchTeams()
.then(() => {
router.push("/");
})
});
}
</script>
<template>
<div class="member-list-header">
<h2>Members</h2>
<em class="aside" v-if="teamMembers">
{{ teamMembers?.length }} member(s),
{{ availableMembers?.length }} currently available,
{{ availableMembersNextHour?.length }} available in the next hour
</em>
<div class="team-details-button-group">
<RouterLink class="button" :to="'/schedule?teamId=' + team.id">
<button class="accent">
<i class="bi bi-calendar-fill margin"></i>
View schedule
</button>
</RouterLink>
<button
class="destructive"
@click="leaveTeam"
>
Leave
</button>
</div>
</div>
<table class="member-table">
<tbody>
<PlayerTeamCard
v-for="member in teamMembers"
:player="member"
:team="team"
:key="member.username"
/>
</tbody>
</table>
<h2>Active Invites</h2>
<div>
<details>
<summary>View all invites</summary>
<span v-if="invites?.length == 0">
There are currently no active invites to this team.
</span>
<table id="invite-table" v-else>
<thead>
<tr>
<th>
Key (hover to reveal)
</th>
<th>
Creation time
</th>
</tr>
</thead>
<tbody>
<InviteEntry
v-for="invite in invites"
:invite="invite"
/>
</tbody>
</table>
<div class="create-invite-group">
<button class="accent" @click="createInvite">
<i class="bi bi-person-fill-add margin" />
Create Invite
</button>
<span class="small aside">
Invites are usable once and expire after 24 hours.
</span>
</div>
</details>
</div>
</template>
<style scoped>
.member-list-header {
display: flex;
gap: 0.5em;
align-items: center;
}
.member-list-header > .aside {
font-size: 12pt;
font-style: normal;
}
table.member-table {
width: 100%;
}
table.member-table th {
text-align: left;
padding-left: 2em;
font-weight: 700;
}
th {
text-align: left;
font-weight: 600;
padding: 8px;
}
#invite-table {
width: 100%;
border: 1px solid var(--text);
margin: 8px 0;
}
.team-details-button-group {
flex: 1;
display: flex;
align-items: center;
justify-content: end;
gap: 4px;
}
.create-invite-group {
display: flex;
gap: 8px;
align-items: center;
}
</style>

View File

@ -1,18 +1,14 @@
<script setup lang="ts">
import { useRoute, useRouter, RouterLink } from "vue-router";
import { useRoute, useRouter, RouterLink, RouterView } from "vue-router";
import { useTeamsStore } from "../stores/teams";
import { computed, onMounted, ref } from "vue";
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
import InviteEntry from "../components/InviteEntry.vue";
import { useTeamDetails } from "../composables/team-details";
import moment from "moment";
const route = useRoute();
const router = useRouter();
const teamsStore = useTeamsStore();
const team = computed(() => {
return teamsStore.teams[route.params.id];
});
const { team, teamId } = useTeamDetails();
const creationDate = computed(() => {
if (team.value) {
@ -20,55 +16,21 @@ const creationDate = computed(() => {
}
});
const invites = computed(() => {
return teamsStore.teamInvites[route.params.id];
});
const availableMembers = computed(() => {
return teamsStore.teamMembers[route.params.id]
.filter((member) => member.availability[0] > 0);
});
const availableMembersNextHour = computed(() => {
return teamsStore.teamMembers[route.params.id]
.filter((member) => member.availability[1] > 0);
});
function createInvite() {
teamsStore.createInvite(team.value.id);
}
function revokeInvite(key) {
teamsStore.revokeInvite(team.value.id, key)
}
function leaveTeam() {
teamsStore.leaveTeam(team.value.id)
.then(() => {
teamsStore.fetchTeams()
.then(() => {
router.push("/");
})
});
}
onMounted(async () => {
let key = route.query.key;
let teamId = route.params.id;
const key = computed(() => route.query.key);
onMounted(() => {
let doFetchTeam = () => {
teamsStore.fetchTeam(teamId)
.then(() => teamsStore.fetchTeamMembers(teamId))
.then(() => teamsStore.getInvites(teamId));
teamsStore.fetchTeam(teamId.value)
.then(() => teamsStore.fetchTeamMembers(teamId.value))
.then(() => teamsStore.getInvites(teamId.value));
};
if (key) {
teamsStore.consumeInvite(teamId, key)
if (key.value) {
teamsStore.consumeInvite(teamId.value, key.value.toString())
.finally(doFetchTeam);
} else {
doFetchTeam();
}
});
</script>
@ -83,90 +45,7 @@ onMounted(async () => {
Formed on {{ creationDate }}
</span>
</center>
<div class="member-list-header">
<h2>Members</h2>
<em class="aside" v-if="teamsStore.teamMembers[route.params.id]">
{{ teamsStore.teamMembers[route.params.id]?.length }} member(s),
{{ availableMembers?.length }} currently available,
{{ availableMembersNextHour?.length }} available in the next hour
</em>
<div class="team-details-button-group">
<RouterLink class="button" :to="'/schedule?teamId=' + team.id">
<button class="accent">
<i class="bi bi-calendar-fill margin"></i>
View schedule
</button>
</RouterLink>
<button
class="destructive"
@click="leaveTeam"
>
Leave
</button>
</div>
</div>
<table class="member-table">
<!--thead>
<tr>
<th>
Name
</th>
<th>
Roles
</th>
<th>
Playtime on team
</th>
<th>
Joined
</th>
</tr>
</thead-->
<tbody>
<PlayerTeamCard
v-for="member in teamsStore.teamMembers[route.params.id]"
:player="member"
:team="team"
:key="member.username"
/>
</tbody>
</table>
<h2>Active Invites</h2>
<div>
<details>
<summary>View all invites</summary>
<span v-if="invites?.length == 0">
There are currently no active invites to this team.
</span>
<table id="invite-table" v-else>
<thead>
<tr>
<th>
Key (hover to reveal)
</th>
<th>
Creation time
</th>
</tr>
</thead>
<tbody>
<InviteEntry
v-for="invite in invites"
:invite="invite"
/>
</tbody>
</table>
<div class="create-invite-group">
<button class="accent" @click="createInvite">
<i class="bi bi-person-fill-add margin" />
Create Invite
</button>
<span class="small aside">
Invites are usable once and expire after 24 hours.
</span>
</div>
</details>
</div>
<RouterView />
</template>
</main>
</template>
@ -175,59 +54,4 @@ onMounted(async () => {
.team-info {
margin: 4em;
}
.member-list-header {
display: flex;
gap: 0.5em;
align-items: center;
}
.member-list-header > .aside {
font-size: 12pt;
font-style: normal;
}
table.member-table {
width: 100%;
}
table.member-table th {
text-align: left;
padding-left: 2em;
font-weight: 700;
}
/*
div.member-grid {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
*/
th {
text-align: left;
font-weight: 600;
padding: 8px;
}
#invite-table {
width: 100%;
border: 1px solid var(--text);
margin: 8px 0;
}
.team-details-button-group {
flex: 1;
display: flex;
align-items: center;
justify-content: end;
gap: 4px;
}
.create-invite-group {
display: flex;
gap: 8px;
align-items: center;
}
</style>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useTeamsStore } from "../stores/teams.ts"
import { useTeamsStore } from "../stores/teams";
import timezones from "../assets/timezones.json";
import { useRouter } from "vue-router";