Redesign team page
							parent
							
								
									b4deeddfba
								
							
						
					
					
						commit
						a0fadfca94
					
				| 
						 | 
					@ -59,6 +59,10 @@ button > i.bi.margin {
 | 
				
			||||||
  margin-right: 4px;
 | 
					  margin-right: 4px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					i.bi.margin {
 | 
				
			||||||
 | 
					  margin-right: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
button:hover {
 | 
					button:hover {
 | 
				
			||||||
  background-color: var(--surface-0);
 | 
					  background-color: var(--surface-0);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -121,6 +125,50 @@ h2 {
 | 
				
			||||||
  font-weight: 800;
 | 
					  font-weight: 800;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details > summary {
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details.accordion {
 | 
				
			||||||
 | 
					  padding: 16px;
 | 
				
			||||||
 | 
					  background-color: var(--mantle);
 | 
				
			||||||
 | 
					  border: 1px solid var(--mantle);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details.accordion[open] {
 | 
				
			||||||
 | 
					  background-color: var(--base);
 | 
				
			||||||
 | 
					  border-color: var(--overlay-0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details.accordion > summary {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  gap: 1em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details > summary::after {
 | 
				
			||||||
 | 
					  content: "›";
 | 
				
			||||||
 | 
					  font-size: 1.5rem;
 | 
				
			||||||
 | 
					  transition-duration: 200ms;
 | 
				
			||||||
 | 
					  margin-left: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details[open] > summary::after {
 | 
				
			||||||
 | 
					  transform: rotate(90deg);
 | 
				
			||||||
 | 
					  transition-duration: 200ms;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					details > summary > h2 {
 | 
				
			||||||
 | 
					  display: inline-block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3 {
 | 
				
			||||||
 | 
					  font-size: 11pt;
 | 
				
			||||||
 | 
					  font-weight: 700;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
span.small {
 | 
					span.small {
 | 
				
			||||||
  font-size: 9pt;
 | 
					  font-size: 9pt;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -145,7 +193,7 @@ input {
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  color: var(--text);
 | 
					  color: var(--text);
 | 
				
			||||||
  padding: 6px 9px;
 | 
					  padding: 7px 9px;
 | 
				
			||||||
  border: none;
 | 
					  border: none;
 | 
				
			||||||
  /*outline: 1px solid var(--overlay-0);*/
 | 
					  /*outline: 1px solid var(--overlay-0);*/
 | 
				
			||||||
  border: 1px solid var(--overlay-0);
 | 
					  border: 1px solid var(--overlay-0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,12 @@
 | 
				
			||||||
import { type TeamInviteSchema } from "../client";
 | 
					import { type TeamInviteSchema } from "../client";
 | 
				
			||||||
import { useTeamsStore } from "../stores/teams";
 | 
					import { useTeamsStore } from "../stores/teams";
 | 
				
			||||||
import { computed, type PropType } from "vue";
 | 
					import { computed, type PropType } from "vue";
 | 
				
			||||||
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teamsStore = useTeamsStore();
 | 
					const teamsStore = useTeamsStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createdAt = computed(() => moment(props.invite.createdAt).format("L LT"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
  invite: {
 | 
					  invite: {
 | 
				
			||||||
    type: Object as PropType<TeamInviteSchema>,
 | 
					    type: Object as PropType<TeamInviteSchema>,
 | 
				
			||||||
| 
						 | 
					@ -37,7 +40,7 @@ function revokeInvite() {
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
    </td>
 | 
					    </td>
 | 
				
			||||||
    <td>
 | 
					    <td>
 | 
				
			||||||
      {{ invite.createdAt }}
 | 
					      {{ createdAt }}
 | 
				
			||||||
    </td>
 | 
					    </td>
 | 
				
			||||||
    <td class="buttons">
 | 
					    <td class="buttons">
 | 
				
			||||||
      <button @click="copyLink">
 | 
					      <button @click="copyLink">
 | 
				
			||||||
| 
						 | 
					@ -45,8 +48,7 @@ function revokeInvite() {
 | 
				
			||||||
        Copy Link
 | 
					        Copy Link
 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
      <button class="destructive" @click="revokeInvite">
 | 
					      <button class="destructive" @click="revokeInvite">
 | 
				
			||||||
        <i class="bi bi-trash margin" />
 | 
					        <i class="bi bi-trash" />
 | 
				
			||||||
        Revoke
 | 
					 | 
				
			||||||
      </button>
 | 
					      </button>
 | 
				
			||||||
    </td>
 | 
					    </td>
 | 
				
			||||||
  </tr>
 | 
					  </tr>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ import { useRoute, useRouter, RouterLink } from "vue-router";
 | 
				
			||||||
import { computed } from "vue";
 | 
					import { computed } from "vue";
 | 
				
			||||||
import { useTeamDetails } from "../composables/team-details";
 | 
					import { useTeamDetails } from "../composables/team-details";
 | 
				
			||||||
import PlayerTeamCard from "../components/PlayerTeamCard.vue";
 | 
					import PlayerTeamCard from "../components/PlayerTeamCard.vue";
 | 
				
			||||||
import InviteEntry from "../components/InviteEntry.vue";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
const router = useRouter();
 | 
					const router = useRouter();
 | 
				
			||||||
| 
						 | 
					@ -17,10 +16,6 @@ const {
 | 
				
			||||||
  teamMembers,
 | 
					  teamMembers,
 | 
				
			||||||
} = useTeamDetails();
 | 
					} = useTeamDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createInvite() {
 | 
					 | 
				
			||||||
  teamsStore.createInvite(team.value.id);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function leaveTeam() {
 | 
					function leaveTeam() {
 | 
				
			||||||
  teamsStore.leaveTeam(team.value.id)
 | 
					  teamsStore.leaveTeam(team.value.id)
 | 
				
			||||||
    .then(() => {
 | 
					    .then(() => {
 | 
				
			||||||
| 
						 | 
					@ -34,7 +29,10 @@ function leaveTeam() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="member-list-header">
 | 
					  <div class="member-list-header">
 | 
				
			||||||
    <h2>Members</h2>
 | 
					    <h2>
 | 
				
			||||||
 | 
					      <i class="bi bi-people-fill margin" />
 | 
				
			||||||
 | 
					      Members
 | 
				
			||||||
 | 
					    </h2>
 | 
				
			||||||
    <em class="aside" v-if="teamMembers">
 | 
					    <em class="aside" v-if="teamMembers">
 | 
				
			||||||
      {{ teamMembers?.length }} member(s),
 | 
					      {{ teamMembers?.length }} member(s),
 | 
				
			||||||
      {{ availableMembers?.length }} currently available,
 | 
					      {{ availableMembers?.length }} currently available,
 | 
				
			||||||
| 
						 | 
					@ -65,42 +63,6 @@ function leaveTeam() {
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    </tbody>
 | 
					    </tbody>
 | 
				
			||||||
  </table>
 | 
					  </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>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
| 
						 | 
					@ -125,18 +87,6 @@ table.member-table th {
 | 
				
			||||||
  font-weight: 700;
 | 
					  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 {
 | 
					.team-details-button-group {
 | 
				
			||||||
  flex: 1;
 | 
					  flex: 1;
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
| 
						 | 
					@ -3,14 +3,20 @@ import type { PlayerTeamRole } from "../player";
 | 
				
			||||||
import { computed, type PropType, ref, watch } from "vue";
 | 
					import { computed, type PropType, ref, watch } from "vue";
 | 
				
			||||||
import { useTeamsStore } from "../stores/teams";
 | 
					import { useTeamsStore } from "../stores/teams";
 | 
				
			||||||
import { useRosterStore } from "../stores/roster";
 | 
					import { useRosterStore } from "../stores/roster";
 | 
				
			||||||
import { type ViewTeamMembersResponse, type TeamSchema } from "@/client";
 | 
					import { type ViewTeamMembersResponse, type TeamSchema, RoleSchema } from "@/client";
 | 
				
			||||||
import SvgIcon from "@jamescoyle/vue-icon";
 | 
					import SvgIcon from "@jamescoyle/vue-icon";
 | 
				
			||||||
import { mdiCrown } from "@mdi/js";
 | 
					import { mdiCrown } from "@mdi/js";
 | 
				
			||||||
import RoleTag from "../components/RoleTag.vue";
 | 
					import RoleTag from "../components/RoleTag.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps({
 | 
					const props = defineProps({
 | 
				
			||||||
  player: Object as PropType<ViewTeamMembersResponse>,
 | 
					  player: {
 | 
				
			||||||
  team: Object as PropType<TeamSchema>,
 | 
					    type: Object as PropType<ViewTeamMembersResponse>,
 | 
				
			||||||
 | 
					    required: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  team: {
 | 
				
			||||||
 | 
					    type: Object as PropType<TeamSchema>,
 | 
				
			||||||
 | 
					    required: true,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const teamsStore = useTeamsStore();
 | 
					const teamsStore = useTeamsStore();
 | 
				
			||||||
| 
						 | 
					@ -31,8 +37,8 @@ const rosterStore = useRosterStore();
 | 
				
			||||||
const isEditing = ref(false);
 | 
					const isEditing = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// this is the roles of the player we are editing
 | 
					// this is the roles of the player we are editing
 | 
				
			||||||
const roles = ref([]);
 | 
					const roles = ref<(RoleSchema | undefined)[]>([]);
 | 
				
			||||||
const updatedRoles = ref([]);
 | 
					const updatedRoles = ref<RoleSchema[]>([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//const rolesMap = reactive({
 | 
					//const rolesMap = reactive({
 | 
				
			||||||
//  "Role.PocketScout": undefined,
 | 
					//  "Role.PocketScout": undefined,
 | 
				
			||||||
| 
						 | 
					@ -55,7 +61,8 @@ const possibleRoles = [
 | 
				
			||||||
watch(isEditing, (newValue) => {
 | 
					watch(isEditing, (newValue) => {
 | 
				
			||||||
  if (newValue) {
 | 
					  if (newValue) {
 | 
				
			||||||
    // editing
 | 
					    // editing
 | 
				
			||||||
    roles.value = possibleRoles.map((roleName) => {
 | 
					    roles.value = possibleRoles
 | 
				
			||||||
 | 
					      .map((roleName) => {
 | 
				
			||||||
        console.log(roleName);
 | 
					        console.log(roleName);
 | 
				
			||||||
        return props.player.roles
 | 
					        return props.player.roles
 | 
				
			||||||
          .find((playerRole) => playerRole.role == roleName) ?? undefined;
 | 
					          .find((playerRole) => playerRole.role == roleName) ?? undefined;
 | 
				
			||||||
| 
						 | 
					@ -65,7 +72,7 @@ watch(isEditing, (newValue) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function updateRoles() {
 | 
					function updateRoles() {
 | 
				
			||||||
  isEditing.value = false;
 | 
					  isEditing.value = false;
 | 
				
			||||||
  updatedRoles.value = roles.value.filter(x => x);
 | 
					  updatedRoles.value = roles.value.filter((x): x is RoleSchema => !!x);
 | 
				
			||||||
  props.player.roles = updatedRoles.value;
 | 
					  props.player.roles = updatedRoles.value;
 | 
				
			||||||
  console.log(roles.value);
 | 
					  console.log(roles.value);
 | 
				
			||||||
  console.log(updatedRoles.value);
 | 
					  console.log(updatedRoles.value);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { ref, watch } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useTeamSettings() {
 | 
					export function useTeamSettings() {
 | 
				
			||||||
  const teamName = ref("");
 | 
					  const teamName = ref("");
 | 
				
			||||||
| 
						 | 
					@ -7,4 +7,16 @@ export function useTeamSettings() {
 | 
				
			||||||
    Intl.DateTimeFormat().resolvedOptions().timeZone ??
 | 
					    Intl.DateTimeFormat().resolvedOptions().timeZone ??
 | 
				
			||||||
      "Etc/UTC"
 | 
					      "Etc/UTC"
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const minuteOffset = ref(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  watch(minuteOffset, (newValue) => {
 | 
				
			||||||
 | 
					    minuteOffset.value = Math.min(Math.max(0, newValue), 59);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    teamName,
 | 
				
			||||||
 | 
					    timezone,
 | 
				
			||||||
 | 
					    minuteOffset,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,11 @@ import LoginView from "../views/LoginView.vue";
 | 
				
			||||||
import TeamRegistrationView from "../views/TeamRegistrationView.vue";
 | 
					import TeamRegistrationView from "../views/TeamRegistrationView.vue";
 | 
				
			||||||
import TeamDetailsView from "../views/TeamDetailsView.vue";
 | 
					import TeamDetailsView from "../views/TeamDetailsView.vue";
 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
import TeamDetailsMembersListView from "../views/TeamDetailsMembersListView.vue";
 | 
					//import TeamDetailsMembersListView from "../views/TeamDetailsMembersListView.vue";
 | 
				
			||||||
 | 
					import TeamSettingsView from "@/views/TeamSettingsView.vue";
 | 
				
			||||||
 | 
					import TeamSettingsGeneralView from "@/views/TeamSettings/GeneralView.vue";
 | 
				
			||||||
 | 
					import TeamSettingsIntegrationsView from "@/views/TeamSettings/IntegrationsView.vue";
 | 
				
			||||||
 | 
					import TeamSettingsInvitesView from "@/views/TeamSettings/InvitesView.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = createRouter({
 | 
					const router = createRouter({
 | 
				
			||||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
					  history: createWebHistory(import.meta.env.BASE_URL),
 | 
				
			||||||
| 
						 | 
					@ -40,21 +44,32 @@ const router = createRouter({
 | 
				
			||||||
      path: "/team/id/:id",
 | 
					      path: "/team/id/:id",
 | 
				
			||||||
      name: "team-details",
 | 
					      name: "team-details",
 | 
				
			||||||
      component: TeamDetailsView,
 | 
					      component: TeamDetailsView,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      path: "/team/id/:id/settings",
 | 
				
			||||||
 | 
					      name: "team-settings",
 | 
				
			||||||
 | 
					      component: TeamSettingsView,
 | 
				
			||||||
      children: [
 | 
					      children: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          path: "",
 | 
					          path: "",
 | 
				
			||||||
          component: TeamDetailsMembersListView,
 | 
					          name: "team-settings/",
 | 
				
			||||||
 | 
					          component: TeamSettingsGeneralView,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          path: "",
 | 
					          path: "integrations",
 | 
				
			||||||
          component: TeamDetailsMembersListView,
 | 
					          name: "team-settings/integrations",
 | 
				
			||||||
 | 
					          component: TeamSettingsIntegrationsView,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          path: "invites",
 | 
				
			||||||
 | 
					          name: "team-settings/invites",
 | 
				
			||||||
 | 
					          component: TeamSettingsInvitesView,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  ]
 | 
					  ]
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
router
 | 
					router
 | 
				
			||||||
  .beforeEach(async (to, from) => {
 | 
					  .beforeEach(async (to, from) => {
 | 
				
			||||||
    const authStore = useAuthStore();
 | 
					    const authStore = useAuthStore();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,7 +71,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
      .sort(comparator);
 | 
					      .sort(comparator);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const roleIcons = reactive({
 | 
					  const roleIcons = reactive<{ [key: string]: string }>({
 | 
				
			||||||
    "PocketScout": "tf2-PocketScout",
 | 
					    "PocketScout": "tf2-PocketScout",
 | 
				
			||||||
    "FlankScout": "tf2-FlankScout",
 | 
					    "FlankScout": "tf2-FlankScout",
 | 
				
			||||||
    "PocketSoldier": "tf2-PocketSoldier",
 | 
					    "PocketSoldier": "tf2-PocketSoldier",
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,7 @@ export const useRosterStore = defineStore("roster", () => {
 | 
				
			||||||
    "Medic": "tf2-Medic",
 | 
					    "Medic": "tf2-Medic",
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const roleNames = reactive({
 | 
					  const roleNames = reactive<{ [key: string]: string }>({
 | 
				
			||||||
    "PocketScout": "Pocket Scout",
 | 
					    "PocketScout": "Pocket Scout",
 | 
				
			||||||
    "FlankScout": "Flank Scout",
 | 
					    "FlankScout": "Flank Scout",
 | 
				
			||||||
    "PocketSoldier": "Pocket Soldier",
 | 
					    "PocketSoldier": "Pocket Soldier",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,9 +73,9 @@ export const useTeamsStore = defineStore("teams", () => {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async function updateRoles(teamId: number, playerId: number, roles: RoleSchema[]) {
 | 
					  async function updateRoles(teamId: number, playerId: string, roles: RoleSchema[]) {
 | 
				
			||||||
    return await client.default
 | 
					    return await client.default
 | 
				
			||||||
      .editMemberRoles(teamId.toString(), playerId.toString(), {
 | 
					      .editMemberRoles(teamId.toString(), playerId, {
 | 
				
			||||||
        roles,
 | 
					        roles,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import { useRoute, useRouter, RouterLink, RouterView } from "vue-router";
 | 
				
			||||||
import { useTeamsStore } from "../stores/teams";
 | 
					import { useTeamsStore } from "../stores/teams";
 | 
				
			||||||
import { computed, onMounted, ref } from "vue";
 | 
					import { computed, onMounted, ref } from "vue";
 | 
				
			||||||
import { useTeamDetails } from "../composables/team-details";
 | 
					import { useTeamDetails } from "../composables/team-details";
 | 
				
			||||||
 | 
					import MembersList from "../components/MembersList.vue";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
| 
						 | 
					@ -37,7 +38,7 @@ onMounted(() => {
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <main>
 | 
					  <main>
 | 
				
			||||||
    <template v-if="team">
 | 
					    <template v-if="team">
 | 
				
			||||||
      <center class="team-info">
 | 
					      <center class="margin">
 | 
				
			||||||
        <h1>
 | 
					        <h1>
 | 
				
			||||||
          {{ team.teamName }}
 | 
					          {{ team.teamName }}
 | 
				
			||||||
        </h1>
 | 
					        </h1>
 | 
				
			||||||
| 
						 | 
					@ -45,13 +46,18 @@ onMounted(() => {
 | 
				
			||||||
          Formed on {{ creationDate }}
 | 
					          Formed on {{ creationDate }}
 | 
				
			||||||
        </span>
 | 
					        </span>
 | 
				
			||||||
      </center>
 | 
					      </center>
 | 
				
			||||||
      <RouterView />
 | 
					      <center class="margin">
 | 
				
			||||||
 | 
					        <RouterLink :to="{ name: 'team-settings' }">
 | 
				
			||||||
 | 
					          Settings
 | 
				
			||||||
 | 
					        </RouterLink>
 | 
				
			||||||
 | 
					      </center>
 | 
				
			||||||
 | 
					      <MembersList />
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
  </main>
 | 
					  </main>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
.team-info {
 | 
					.margin {
 | 
				
			||||||
  margin: 4em;
 | 
					  margin: 4em;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,13 +75,6 @@ function createTeam() {
 | 
				
			||||||
          past the hour.
 | 
					          past the hour.
 | 
				
			||||||
        </em>
 | 
					        </em>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="form-group margin">
 | 
					 | 
				
			||||||
        <h3>
 | 
					 | 
				
			||||||
          Announcements Webhook URL
 | 
					 | 
				
			||||||
          <span class="aside">(optional)</span>
 | 
					 | 
				
			||||||
        </h3>
 | 
					 | 
				
			||||||
        <input v-model="webhook" />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div class="form-group margin">
 | 
					      <div class="form-group margin">
 | 
				
			||||||
        <div class="action-buttons">
 | 
					        <div class="action-buttons">
 | 
				
			||||||
          <button class="accent" @click="createTeam">Create team</button>
 | 
					          <button class="accent" @click="createTeam">Create team</button>
 | 
				
			||||||
| 
						 | 
					@ -98,11 +91,6 @@ function createTeam() {
 | 
				
			||||||
  margin: auto;
 | 
					  margin: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.team-registration-container h3 {
 | 
					 | 
				
			||||||
  font-size: 11pt;
 | 
					 | 
				
			||||||
  font-weight: 700;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.team-registration-container .aside {
 | 
					.team-registration-container .aside {
 | 
				
			||||||
  font-size: 9pt;
 | 
					  font-size: 9pt;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useTeamSettings } from '@/composables/team-settings';
 | 
				
			||||||
 | 
					import timezones from "@/assets/timezones.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {
 | 
				
			||||||
 | 
					  teamName,
 | 
				
			||||||
 | 
					  timezone,
 | 
				
			||||||
 | 
					  minuteOffset,
 | 
				
			||||||
 | 
					} = useTeamSettings();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="team-general-settings">
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <div class="form-group margin">
 | 
				
			||||||
 | 
					        <h3 class="closer">Team Name</h3>
 | 
				
			||||||
 | 
					        <input v-model="teamName" />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="form-group margin">
 | 
				
			||||||
 | 
					        <div class="form-group row">
 | 
				
			||||||
 | 
					          <div class="form-group">
 | 
				
			||||||
 | 
					            <h3>
 | 
				
			||||||
 | 
					              Timezone
 | 
				
			||||||
 | 
					              <a
 | 
				
			||||||
 | 
					                class="aside"
 | 
				
			||||||
 | 
					                href="https://nodatime.org/TimeZones"
 | 
				
			||||||
 | 
					                target="_blank"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                (view all timezones)
 | 
				
			||||||
 | 
					              </a>
 | 
				
			||||||
 | 
					            </h3>
 | 
				
			||||||
 | 
					            <v-select :options="timezones" v-model="timezone" />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div class="form-group" id="minute-offset-group">
 | 
				
			||||||
 | 
					            <h3>Minute Offset</h3>
 | 
				
			||||||
 | 
					            <input type="number" v-model="minuteOffset" min="0" max="59" />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <em class="aside">
 | 
				
			||||||
 | 
					          Matches will be scheduled based on {{ timezone }} at
 | 
				
			||||||
 | 
					          {{ minuteOffset }}
 | 
				
			||||||
 | 
					          <span v-if="minuteOffset == 1">
 | 
				
			||||||
 | 
					            minute
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					          <span v-else>
 | 
				
			||||||
 | 
					            minutes
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					          past the hour.
 | 
				
			||||||
 | 
					        </em>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="form-group margin">
 | 
				
			||||||
 | 
					        <div class="action-buttons">
 | 
				
			||||||
 | 
					          <button class="accent" @click="updateTeamSettings">Save</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="team-integrations">
 | 
				
			||||||
 | 
					    This team currently does not have any integrations.
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useTeamDetails } from "@/composables/team-details";
 | 
				
			||||||
 | 
					import { useTeamsStore } from "@/stores/teams";
 | 
				
			||||||
 | 
					import { onMounted } from "vue";
 | 
				
			||||||
 | 
					import InviteEntry from "@/components/InviteEntry.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const teamsStore = useTeamsStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {
 | 
				
			||||||
 | 
					  team,
 | 
				
			||||||
 | 
					  teamId,
 | 
				
			||||||
 | 
					  invites,
 | 
				
			||||||
 | 
					} = useTeamDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createInvite() {
 | 
				
			||||||
 | 
					  teamsStore.createInvite(team.value.id);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  teamsStore.fetchTeam(teamId.value)
 | 
				
			||||||
 | 
					    .then(() => teamsStore.getInvites(teamId.value));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="invites" v-if="team">
 | 
				
			||||||
 | 
					    <table id="invite-table" v-if="invites?.length > 0">
 | 
				
			||||||
 | 
					      <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>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					#invite-table {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  border: 1px solid var(--overlay-0);
 | 
				
			||||||
 | 
					  border-radius: 8px;
 | 
				
			||||||
 | 
					  margin: 8px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#invite-table th {
 | 
				
			||||||
 | 
					  text-align: left;
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  padding: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,97 @@
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useTeamsStore } from "@/stores/teams";
 | 
				
			||||||
 | 
					import { useTeamDetails } from "@/composables/team-details";
 | 
				
			||||||
 | 
					import { onMounted } from "vue";
 | 
				
			||||||
 | 
					import { RouterLink, RouterView } from "vue-router";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const teamsStore = useTeamsStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {
 | 
				
			||||||
 | 
					  team,
 | 
				
			||||||
 | 
					  teamId,
 | 
				
			||||||
 | 
					} = useTeamDetails();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(() => {
 | 
				
			||||||
 | 
					  teamsStore.fetchTeam(teamId.value);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <main class="team-settings" v-if="team">
 | 
				
			||||||
 | 
					    <nav class="sidebar">
 | 
				
			||||||
 | 
					      <div class="categories">
 | 
				
			||||||
 | 
					        <div class="back-link">
 | 
				
			||||||
 | 
					          <RouterLink :to="{ name: 'team-details' }">
 | 
				
			||||||
 | 
					            <i class="bi bi-arrow-left-short" />
 | 
				
			||||||
 | 
					            {{ team.teamName }}
 | 
				
			||||||
 | 
					          </RouterLink>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <h3>Settings</h3>
 | 
				
			||||||
 | 
					        <RouterLink class="tab" :to="{ name: 'team-settings/' }">
 | 
				
			||||||
 | 
					          Overview
 | 
				
			||||||
 | 
					        </RouterLink>
 | 
				
			||||||
 | 
					        <RouterLink class="tab" :to="{ name: 'team-settings/integrations' }">
 | 
				
			||||||
 | 
					          Integrations
 | 
				
			||||||
 | 
					        </RouterLink>
 | 
				
			||||||
 | 
					        <RouterLink class="tab" :to="{ name: 'team-settings/invites' }">
 | 
				
			||||||
 | 
					          Invites
 | 
				
			||||||
 | 
					        </RouterLink>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					    <div class="view">
 | 
				
			||||||
 | 
					      <RouterView />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </main>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.team-settings {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: 16px;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.team-settings nav.sidebar {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: end;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.team-settings .view {
 | 
				
			||||||
 | 
					  width: 60%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.back-link {
 | 
				
			||||||
 | 
					  padding: 8px 16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.sidebar h3 {
 | 
				
			||||||
 | 
					  text-transform: uppercase;
 | 
				
			||||||
 | 
					  color: var(--overlay-0);
 | 
				
			||||||
 | 
					  padding: 0.5em 16px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.sidebar > .categories {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
 | 
					  width: 256px;
 | 
				
			||||||
 | 
					  gap: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.sidebar a.tab {
 | 
				
			||||||
 | 
					  font-size: 12pt;
 | 
				
			||||||
 | 
					  color: var(--overlay-0);
 | 
				
			||||||
 | 
					  padding: 8px 16px;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.sidebar a.tab:hover {
 | 
				
			||||||
 | 
					  background-color: var(--crust);
 | 
				
			||||||
 | 
					  color: var(--text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav.sidebar a.tab.router-link-exact-active {
 | 
				
			||||||
 | 
					  background-color: var(--crust);
 | 
				
			||||||
 | 
					  color: var(--text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
		Loading…
	
		Reference in New Issue