Skip to content

Commit ec37065

Browse files
authored
chore: refactor buttons / public servers page / minor tweaks to some flows (#416)
1 parent 70f5b93 commit ec37065

115 files changed

Lines changed: 5567 additions & 2361 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

components/AvatarUpload.vue

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<script setup lang="ts">
22
import { ref, computed } from "vue";
33
import { toast } from "@/components/ui/toast";
4-
import { Upload, Trash2, Loader2, X } from "lucide-vue-next";
4+
import { Upload, Trash2, X } from "lucide-vue-next";
5+
import { Spinner } from "~/components/ui/spinner";
56
import RosterImageEditor from "@/components/RosterImageEditor.vue";
67
78
interface BulkTeam {
@@ -238,7 +239,7 @@ async function remove() {
238239
:disabled="isUploading || isRemoving"
239240
@click.prevent.stop="remove"
240241
>
241-
<Loader2 v-if="isRemoving" class="h-3 w-3 animate-spin" />
242+
<Spinner v-if="isRemoving" class="h-3 w-3" />
242243
<X v-else class="h-3 w-3" />
243244
</button>
244245
</div>
@@ -258,9 +259,9 @@ async function remove() {
258259
{{ useEditor ? $t("avatar.roster_hint") : $t("avatar.hint") }}
259260
</div>
260261
</div>
261-
<Loader2
262+
<Spinner
262263
v-if="isUploading"
263-
class="w-5 h-5 animate-spin text-[hsl(var(--tac-amber))]"
264+
class="w-5 h-5 text-[hsl(var(--tac-amber))]"
264265
/>
265266
</div>
266267

@@ -271,7 +272,7 @@ async function remove() {
271272
:disabled="isUploading || isRemoving"
272273
@click.prevent.stop="remove"
273274
>
274-
<Loader2 v-if="isRemoving" class="w-3.5 h-3.5 animate-spin" />
275+
<Spinner v-if="isRemoving" class="w-3.5 h-3.5" />
275276
<Trash2 v-else class="w-3.5 h-3.5" />
276277
<span>{{ $t(removeKey) }}</span>
277278
</button>
@@ -292,7 +293,7 @@ async function remove() {
292293
:disabled="isUploading || isRemoving"
293294
@click.prevent="triggerPicker"
294295
>
295-
<Loader2 v-if="isUploading" class="w-4 h-4 animate-spin" />
296+
<Spinner v-if="isUploading" class="w-4 h-4" />
296297
<Upload v-else class="w-4 h-4" />
297298
<span>{{ hasCustom ? $t(changeKey) : $t(uploadKey) }}</span>
298299
</button>
@@ -303,7 +304,7 @@ async function remove() {
303304
:disabled="isUploading || isRemoving"
304305
@click.prevent="remove"
305306
>
306-
<Loader2 v-if="isRemoving" class="w-4 h-4 animate-spin" />
307+
<Spinner v-if="isRemoving" class="w-4 h-4" />
307308
<Trash2 v-else class="w-4 h-4" />
308309
<span>{{ $t(removeKey) }}</span>
309310
</button>

components/ClipBoard.vue

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
<script lang="ts" setup>
2-
import { Copy } from "lucide-vue-next";
2+
import { Copy, Check } from "lucide-vue-next";
33
</script>
44
<template>
55
<Button variant="outline" size="icon" @click="copyToClipboard">
6-
<div ref="copy" :data-clipboard-text="data">
7-
<slot>
8-
<Copy></Copy>
9-
</slot>
6+
<div
7+
ref="copy"
8+
:data-clipboard-text="data"
9+
class="relative inline-flex items-center justify-center"
10+
>
11+
<Transition
12+
enter-active-class="transition-all duration-300 ease-out"
13+
leave-active-class="transition-all duration-200 ease-in"
14+
enter-from-class="opacity-0 scale-50 -rotate-90"
15+
enter-to-class="opacity-100 scale-100 rotate-0"
16+
leave-from-class="opacity-100 scale-100"
17+
leave-to-class="opacity-0 scale-50"
18+
>
19+
<span
20+
v-if="copied"
21+
class="absolute inset-0 flex items-center justify-center"
22+
>
23+
<Check class="text-green-500" />
24+
</span>
25+
</Transition>
26+
<span
27+
class="inline-flex items-center justify-center transition-all duration-300 ease-out"
28+
:class="copied ? 'scale-50 opacity-0 blur-[1px]' : ''"
29+
>
30+
<slot>
31+
<Copy></Copy>
32+
</slot>
33+
</span>
1034
</div>
1135
</Button>
1236
</template>
@@ -21,8 +45,21 @@ export default {
2145
default: "",
2246
},
2347
},
48+
data() {
49+
return {
50+
copied: false,
51+
};
52+
},
53+
beforeUnmount() {
54+
if (this.copiedTimer) {
55+
clearTimeout(this.copiedTimer);
56+
}
57+
},
2458
methods: {
25-
async copyToClipboard() {
59+
copyToClipboard() {
60+
void this.performCopy();
61+
},
62+
async performCopy() {
2663
if (!this.data) {
2764
console.warn("data missing");
2865
return;
@@ -34,17 +71,26 @@ export default {
3471
} else {
3572
this.fallbackCopyToClipboard(this.data);
3673
}
37-
38-
toast({
39-
title: this.$t("pages.toasts.copied_to_clipboard"),
40-
});
4174
} catch (err) {
4275
console.error("Failed to copy to clipboard:", err);
4376
this.fallbackCopyToClipboard(this.data);
44-
toast({
45-
title: this.$t("pages.toasts.copied_to_clipboard"),
46-
});
4777
}
78+
79+
this.showCopied();
80+
toast({
81+
title: this.$t("pages.toasts.copied_to_clipboard"),
82+
});
83+
},
84+
85+
showCopied() {
86+
this.copied = true;
87+
if (this.copiedTimer) {
88+
clearTimeout(this.copiedTimer);
89+
}
90+
this.copiedTimer = setTimeout(() => {
91+
this.copied = false;
92+
this.copiedTimer = undefined;
93+
}, 1500);
4894
},
4995
5096
fallbackCopyToClipboard(text) {

components/KickPlayer.vue

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
import { useI18n } from "vue-i18n";
4+
import gql from "graphql-tag";
5+
import { LogOut } from "lucide-vue-next";
6+
import { Button } from "~/components/ui/button";
7+
import { Input } from "~/components/ui/input";
8+
import { Label } from "~/components/ui/label";
9+
import {
10+
Dialog,
11+
DialogContent,
12+
DialogDescription,
13+
DialogFooter,
14+
DialogHeader,
15+
DialogTitle,
16+
} from "~/components/ui/dialog";
17+
import { toast } from "@/components/ui/toast";
18+
19+
const props = defineProps<{
20+
player: Record<string, any>;
21+
serverId: string;
22+
}>();
23+
24+
const emit = defineEmits<{ (e: "kicked"): void }>();
25+
26+
const { t } = useI18n();
27+
const nuxtApp = useNuxtApp();
28+
29+
const open = ref(false);
30+
const reason = ref("");
31+
const submitting = ref(false);
32+
33+
const kickMutation = gql`
34+
mutation KickServerPlayer(
35+
$serverId: String!
36+
$steam_id: String!
37+
$reason: String
38+
) {
39+
kickServerPlayer(
40+
serverId: $serverId
41+
steam_id: $steam_id
42+
reason: $reason
43+
) {
44+
kicked
45+
message
46+
}
47+
}
48+
`;
49+
50+
async function kick() {
51+
if (submitting.value) {
52+
return;
53+
}
54+
55+
submitting.value = true;
56+
57+
try {
58+
const { data } = await nuxtApp.$apollo.defaultClient.mutate({
59+
mutation: kickMutation,
60+
variables: {
61+
serverId: props.serverId,
62+
steam_id: props.player.steam_id,
63+
reason: reason.value || null,
64+
},
65+
});
66+
67+
const result = data?.kickServerPlayer;
68+
69+
if (result?.kicked) {
70+
toast({ title: t("player.kick.kicked", { name: props.player.name }) });
71+
} else {
72+
toast({
73+
variant: "destructive",
74+
title: t("common.error"),
75+
description: result?.message,
76+
});
77+
}
78+
79+
open.value = false;
80+
reason.value = "";
81+
emit("kicked");
82+
} catch (error: any) {
83+
toast({
84+
variant: "destructive",
85+
title: t("common.error"),
86+
description: error?.message,
87+
});
88+
} finally {
89+
submitting.value = false;
90+
}
91+
}
92+
</script>
93+
94+
<template>
95+
<button
96+
type="button"
97+
:title="$t('player.kick.button')"
98+
:aria-label="$t('player.kick.button')"
99+
class="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded border border-amber-500/45 bg-amber-500/10 text-amber-400 transition-colors hover:border-amber-500/80 hover:bg-amber-500/20 hover:text-amber-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background"
100+
@click="open = true"
101+
>
102+
<LogOut class="h-4 w-4" />
103+
</button>
104+
105+
<Dialog :open="open" @update:open="open = $event">
106+
<DialogContent>
107+
<DialogHeader>
108+
<DialogTitle>
109+
{{ $t("player.kick.title", { name: player.name }) }}
110+
</DialogTitle>
111+
<DialogDescription>
112+
{{ $t("player.kick.description") }}
113+
</DialogDescription>
114+
</DialogHeader>
115+
116+
<div class="grid gap-2 py-2">
117+
<Label>{{ $t("player.kick.reason_label") }}</Label>
118+
<Input
119+
v-model="reason"
120+
:placeholder="$t('player.kick.reason_placeholder')"
121+
@keyup.enter="kick"
122+
/>
123+
</div>
124+
125+
<DialogFooter>
126+
<Button variant="outline" @click="open = false">
127+
{{ $t("common.cancel") }}
128+
</Button>
129+
<Button :loading="submitting" @click="kick">
130+
{{ $t("player.kick.confirm") }}
131+
</Button>
132+
</DialogFooter>
133+
</DialogContent>
134+
</Dialog>
135+
</template>

components/MatchTableRow.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,6 +1779,16 @@ export default {
17791779
17801780
return null;
17811781
},
1782+
isPartyLeader(): boolean {
1783+
const lobby = useMatchmakingStore().currentLobby as any;
1784+
if (!lobby) {
1785+
return true;
1786+
}
1787+
const me = lobby.players?.find((p: any) => {
1788+
return p.player.steam_id === useAuthStore().me?.steam_id;
1789+
});
1790+
return !!me?.captain;
1791+
},
17821792
canJoinMatch(): boolean {
17831793
const hasAvailableSpot =
17841794
(this.match.lineup_counts?.lineup_1_count ?? 0) <
@@ -1787,6 +1797,7 @@ export default {
17871797
this.match.max_players_per_lineup;
17881798
17891799
return (
1800+
this.isPartyLeader &&
17901801
this.match.status === e_match_status_enum.PickingPlayers &&
17911802
this.match.options.lobby_access === e_lobby_access_enum.Open &&
17921803
!this.match.lineup_1.is_on_lineup &&

components/PlayerChangeCountry.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
2-
import { Check, ChevronsUpDown, Loader2 } from "lucide-vue-next";
2+
import { Check, ChevronsUpDown } from "lucide-vue-next";
3+
import { Spinner } from "~/components/ui/spinner";
34
import TimezoneFlag from "~/components/TimezoneFlag.vue";
45
</script>
56

@@ -63,7 +64,7 @@ import TimezoneFlag from "~/components/TimezoneFlag.vue";
6364
</Command>
6465
</PopoverContent>
6566
</Popover>
66-
<Loader2 v-if="saving" class="h-4 w-4 animate-spin text-muted-foreground" />
67+
<Spinner v-if="saving" class="h-4 w-4 text-muted-foreground" />
6768
</div>
6869
</template>
6970

components/PlayerChangeName.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { Input } from "~/components/ui/input";
33
import { Button } from "~/components/ui/button";
4-
import { Loader2 } from "lucide-vue-next";
4+
import { Spinner } from "~/components/ui/spinner";
55
</script>
66

77
<template>
@@ -19,9 +19,10 @@ import { Loader2 } from "lucide-vue-next";
1919
<Button
2020
type="submit"
2121
size="sm"
22+
:loading="saving"
2223
:disabled="saving || !isValid || name === player.name"
2324
>
24-
<Loader2 v-if="saving" class="mr-1 h-4 w-4 animate-spin" />
25+
<Spinner v-if="saving" class="mr-1 h-4 w-4" />
2526
{{ $t("common.save") }}
2627
</Button>
2728
</form>

components/PlayerDisplay.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import SteamIcon from "~/components/icons/SteamIcon.vue";
55
import PlayerElo from "~/components/PlayerElo.vue";
66
import PlayerPremierRank from "~/components/PlayerPremierRank.vue";
77
import PlayerSkillGroupRank from "~/components/PlayerSkillGroupRank.vue";
8-
import { Crown, Shield, BadgeCheck, BadgeIcon, Podcast } from "lucide-vue-next";
8+
import {
9+
Crown,
10+
Shield,
11+
ShieldHalf,
12+
BadgeCheck,
13+
BadgeIcon,
14+
Podcast,
15+
} from "lucide-vue-next";
916
import FiveStackToolTip from "./FiveStackToolTip.vue";
1017
</script>
1118
<template>
@@ -176,6 +183,9 @@ import FiveStackToolTip from "./FiveStackToolTip.vue";
176183
<template v-if="isStreamer">
177184
<Podcast class="w-3 h-3 mr-1 text-green-500" />
178185
</template>
186+
<template v-if="isModerator">
187+
<ShieldHalf class="w-3 h-3 mr-1 text-blue-500" />
188+
</template>
179189
<template v-if="isMatchOrganizer">
180190
<Shield class="w-3 h-3 mr-1 text-yellow-500" />
181191
</template>
@@ -406,6 +416,9 @@ export default {
406416
isStreamer() {
407417
return this.player?.role === e_player_roles_enum.streamer;
408418
},
419+
isModerator() {
420+
return this.player?.role === e_player_roles_enum.moderator;
421+
},
409422
isMatchOrganizer() {
410423
return this.player?.role === e_player_roles_enum.match_organizer;
411424
},

0 commit comments

Comments
 (0)