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,17 +61,18 @@ const possibleRoles = [
|
||||||
watch(isEditing, (newValue) => {
|
watch(isEditing, (newValue) => {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
// editing
|
// editing
|
||||||
roles.value = possibleRoles.map((roleName) => {
|
roles.value = possibleRoles
|
||||||
console.log(roleName);
|
.map((roleName) => {
|
||||||
return props.player.roles
|
console.log(roleName);
|
||||||
.find((playerRole) => playerRole.role == roleName) ?? undefined;
|
return props.player.roles
|
||||||
});
|
.find((playerRole) => playerRole.role == roleName) ?? undefined;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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