mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-04-24 14:59:41 -05:00
Finish public profile page, clean up dashboard cards
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
parent
091dfe548e
commit
6e609f72bb
|
|
@ -1,4 +1,4 @@
|
|||
VITE_APP_VERSION="3.0.21"
|
||||
VITE_APP_VERSION="3.0.22"
|
||||
VITE_API_URL="http://10.5.7.5:8000/"
|
||||
VITE_API_KEY="your_api_key_should_be_here"
|
||||
VITE_ASSET_PATH="/assets"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
VITE_APP_VERSION="3.0.21"
|
||||
VITE_APP_VERSION="3.0.22"
|
||||
VITE_API_URL="https://restfulsleep.phaseii.network"
|
||||
VITE_API_KEY="your_api_key_should_be_here"
|
||||
VITE_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/web-assets"
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@
|
|||
"3.0.18": ["- (Admin) Adds the ability to add and remove managers from an arcade.", "- (Minor) Lay groundwork for avatars in tables.", "- (Minor) Add new profile customizations."],
|
||||
"3.0.19": ["- (Admin) Finish admin arcade and machine pages.", "- (Minor) Add new greetings."],
|
||||
"3.0.20": ["- (Major) Add read state to network news.", "- (Minor) Add new greetings.", "- (Minor) Add information to linked services", "- (Minor) Add session info and button to remove all sessions", "- (Minor) Add text gradient animation backend, needs ported to text once finished."],
|
||||
"3.0.21": ["- (Admin) Finish admin machine/arcade pages", "- (Major) Finish initial public profile page", "- (Minor) Change table right-click behavior", "- (Admin) Finish admin News pages", "- (Admin) Add search for user via card ID"]
|
||||
"3.0.21": ["- (Admin) Finish admin machine/arcade pages", "- (Major) Finish initial public profile page", "- (Minor) Change table right-click behavior", "- (Admin) Finish admin News pages", "- (Admin) Add search for user via card ID"],
|
||||
"3.0.22": ["- (Major) Finish first implementation of the public profile page", "- (Minor) Clean up dashboard game stat box", "- (Minor) Load profile name in game stat box"]
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ import { computed } from "vue";
|
|||
import { useRouter } from "vue-router";
|
||||
import CardBoxComponentBody from "@/components/CardBoxComponentBody.vue";
|
||||
import BaseLevel from "@/components/BaseLevel.vue";
|
||||
import PillTag from "@/components/PillTag.vue";
|
||||
import GameIcon from "@/components/GameIcon.vue";
|
||||
import { getGameInfo } from "@/constants";
|
||||
|
||||
|
|
@ -17,6 +16,11 @@ const props = defineProps({
|
|||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
profileName: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -34,15 +38,15 @@ const props = defineProps({
|
|||
const tag = computed(() => {
|
||||
if (props.type === "plays") {
|
||||
return {
|
||||
type: "success",
|
||||
type: "text-emerald-600",
|
||||
};
|
||||
} else if (props.type === "scores") {
|
||||
return {
|
||||
type: "danger",
|
||||
type: "text-red-500",
|
||||
};
|
||||
} else if (props.type === "ranking") {
|
||||
return {
|
||||
type: "warning",
|
||||
type: "text-amber-400",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +57,11 @@ const tag = computed(() => {
|
|||
|
||||
function loadGamePage() {
|
||||
if (!props.disableLocalClick) {
|
||||
router.push(`/games/${props.game}`);
|
||||
if (!props.userId) {
|
||||
router.push(`/games/${props.game}`);
|
||||
} else {
|
||||
router.push(`/games/${props.game}/profiles/${props.userId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,30 +90,34 @@ const cardStyle = `
|
|||
class="md:mr-6 scale-110 md:scale-100"
|
||||
:path="selectedGame.icon"
|
||||
/>
|
||||
<div class="text-center space-y-1 md:text-left md:mr-6">
|
||||
<h2 class="text-xl sr-only sm:not-sr-only">
|
||||
{{ selectedGame.name }}
|
||||
</h2>
|
||||
<h2 class="text-xl font-semibold not-sr-only sm:sr-only -my-2">
|
||||
<div class="text-center space-y-1 md:text-left md:mr-6 w-full">
|
||||
<h2 class="text-xl font-semibold not-sr-only sm:sr-only">
|
||||
{{
|
||||
selectedGame.shortName
|
||||
? selectedGame.shortName
|
||||
: selectedGame.name
|
||||
}}
|
||||
</h2>
|
||||
<div class="grid">
|
||||
<h2 class="text-xl sr-only sm:not-sr-only">
|
||||
{{ selectedGame.name }}
|
||||
</h2>
|
||||
<hr class="my-1 text-gray-500" />
|
||||
<div>
|
||||
<p class="text-sm md:text-md text-gray-300">
|
||||
{{ profileName }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex space-x-2 justify-center md:justify-start pt-2 md:pt-0"
|
||||
>
|
||||
<PillTag :color="tag.type" :label="type" small />
|
||||
<h4 class="text-lg">{{ value }}</h4>
|
||||
<h4 class="text-lg drop-shadow-xl">
|
||||
{{ value }} <span :class="tag.type">{{ type }}</span>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
<div class="text-center md:text-right">
|
||||
<p class="text-sm text-gray-400">
|
||||
{{ profileName }}
|
||||
</p>
|
||||
</div>
|
||||
</BaseLevel>
|
||||
</CardBoxComponentBody>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
mdiCheckDecagramOutline,
|
||||
mdiLinkBoxVariantOutline,
|
||||
mdiAccountCheck,
|
||||
mdiGavel,
|
||||
} from "@mdi/js";
|
||||
import BaseLevel from "@/components/BaseLevel.vue";
|
||||
import UserAvatar from "@/components/UserAvatar.vue";
|
||||
|
|
@ -64,6 +65,7 @@ const cardData = ref({
|
|||
userAvatar: "",
|
||||
userAdmin: false,
|
||||
userPublic: false,
|
||||
userBanned: false,
|
||||
discordRoles: null,
|
||||
userCustomize: null,
|
||||
});
|
||||
|
|
@ -75,6 +77,7 @@ if (props.overrideProfile) {
|
|||
userName: overrideProfile.name,
|
||||
userAvatar: overrideProfile.avatar,
|
||||
userAdmin: overrideProfile.admin,
|
||||
userBanned: overrideProfile.banned,
|
||||
userPublic: overrideProfile.public,
|
||||
discordRoles: overrideProfile.discordRoles,
|
||||
userCustomize: overrideProfile.customize,
|
||||
|
|
@ -87,6 +90,7 @@ if (props.overrideProfile) {
|
|||
userAvatar: mainStore.userAvatar,
|
||||
userAdmin: mainStore.userAdmin,
|
||||
userPublic: mainStore.userPublic,
|
||||
userBanned: mainStore.userBanned,
|
||||
discordRoles: mainStore.discordRoles,
|
||||
userCustomize: mainStore.userCustomize,
|
||||
};
|
||||
|
|
@ -150,6 +154,13 @@ function getCardStyle() {
|
|||
:icon="mdiSecurity"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="cardData.userBanned"
|
||||
label="Banned"
|
||||
color="danger"
|
||||
:icon="mdiGavel"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="cardData.userId < 300"
|
||||
label="Veteran"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export async function APIGetUser(userId) {
|
|||
customize: userData.data?.customize,
|
||||
userScoreStats: userData.scoreStats,
|
||||
public: userData.public,
|
||||
banned: userData.banned,
|
||||
};
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -223,6 +223,43 @@ export async function APIAdminUsers(noData = false) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function APIAdminPutUser(userId, newUser) {
|
||||
try {
|
||||
const data = await mainStore.callApi(
|
||||
`/admin/user/${userId}`,
|
||||
"POST",
|
||||
newUser
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error updating user:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminUpdatePassword(
|
||||
userId,
|
||||
newPassword,
|
||||
confirmPassword
|
||||
) {
|
||||
const mainStore = useMainStore();
|
||||
|
||||
try {
|
||||
const data = await mainStore.callApi(
|
||||
`/admin/user/${userId}/updatePassword`,
|
||||
"POST",
|
||||
{
|
||||
newPassword: newPassword,
|
||||
confirmPassword: confirmPassword,
|
||||
}
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error updating password:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminUserFromCardId(cardId) {
|
||||
try {
|
||||
const data = await mainStore.callApi(`/admin/user/card/${cardId}`);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const useMainStore = defineStore("main", {
|
|||
userCustomize: {},
|
||||
userScoreStats: {},
|
||||
userPublic: false,
|
||||
userBanned: false,
|
||||
|
||||
/* Field focus with ctrl+k (to register only once) */
|
||||
isFieldFocusRegistered: false,
|
||||
|
|
@ -63,6 +64,9 @@ export const useMainStore = defineStore("main", {
|
|||
if (payload.public) {
|
||||
this.userPublic = payload.public;
|
||||
}
|
||||
if (payload.banned) {
|
||||
this.userBanned = payload.banned;
|
||||
}
|
||||
if (payload.data) {
|
||||
this.userData = payload.data;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ const cardBoxes = ref([
|
|||
:key="profile.game"
|
||||
:game="profile.game"
|
||||
:value="profile.data.total_plays"
|
||||
profile-name=""
|
||||
:profile-name="profile?.username"
|
||||
type="plays"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { computed, ref, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { computed, ref, reactive, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiGamepad,
|
||||
|
|
@ -9,6 +9,10 @@ import {
|
|||
mdiGamepadOutline,
|
||||
mdiFire,
|
||||
mdiTrendingUp,
|
||||
mdiShieldEditOutline,
|
||||
mdiInformationOutline,
|
||||
mdiMail,
|
||||
mdiAsterisk,
|
||||
} from "@mdi/js";
|
||||
import SectionMain from "@/components/SectionMain.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
|
|
@ -18,23 +22,35 @@ import UserCard from "@/components/UserCard.vue";
|
|||
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLine from "@/components/SectionTitleLine.vue";
|
||||
import LineChart from "@/components/Charts/LineChart.vue";
|
||||
import PillTag from "@/components/PillTag.vue";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
import FormControl from "@/components/FormControl.vue";
|
||||
import FormCheckRadio from "@/components/FormCheckRadio.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { getGameInfo } from "@/constants";
|
||||
import { generateChartData } from "@/components/Charts/chart.config";
|
||||
import { useMainStore } from "@/stores/main";
|
||||
import { APIGetUser } from "@/stores/api/account";
|
||||
import { APIAdminPutUser, APIAdminUpdatePassword } from "@/stores/api/admin";
|
||||
|
||||
const mainStore = useMainStore();
|
||||
const $route = useRoute();
|
||||
const $router = useRouter();
|
||||
const reqUserId = $route.params.id;
|
||||
const newUser = ref(null);
|
||||
const userProfile = ref(null);
|
||||
const userProfiles = ref(null);
|
||||
const userScoreStats = ref(null);
|
||||
|
||||
async function loadUser() {
|
||||
try {
|
||||
userProfile.value = null;
|
||||
var data = await APIGetUser(reqUserId);
|
||||
if (!data.name) {
|
||||
data.name = "Unclaimed Account";
|
||||
}
|
||||
userProfile.value = data;
|
||||
newUser.value = JSON.parse(JSON.stringify(data));
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user profile data:", error);
|
||||
}
|
||||
|
|
@ -222,6 +238,45 @@ const cardBoxes = ref([
|
|||
number: todayAttempts,
|
||||
},
|
||||
]);
|
||||
|
||||
function pinInput(event) {
|
||||
event.target.value = event.target.value.replace(/\D/g, "");
|
||||
}
|
||||
|
||||
async function adminUpdateUser() {
|
||||
const response = await APIAdminPutUser(reqUserId, newUser.value);
|
||||
if (response.status !== "success") {
|
||||
window.alert("Failed to update user!");
|
||||
} else {
|
||||
loadUser();
|
||||
}
|
||||
}
|
||||
|
||||
const passwordForm = reactive({
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
|
||||
async function adminSubmitPassword() {
|
||||
const response = await APIAdminUpdatePassword(
|
||||
reqUserId,
|
||||
passwordForm.newPassword,
|
||||
passwordForm.confirmPassword
|
||||
);
|
||||
if (response.status == "success") {
|
||||
alert("Password changed.");
|
||||
passwordForm.newPassword = null;
|
||||
passwordForm.confirmPassword = null;
|
||||
await loadUser();
|
||||
} else {
|
||||
alert("Failed to update password. Check that both passwords match.");
|
||||
}
|
||||
}
|
||||
|
||||
const openArcade = (item) => {
|
||||
const arcadeId = item.id;
|
||||
$router.push(`/arcade/${arcadeId}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -235,6 +290,149 @@ const cardBoxes = ref([
|
|||
/>
|
||||
<UserCard class="mb-6" :override-profile="userProfile" use-small />
|
||||
|
||||
<template v-if="mainStore.userAdmin">
|
||||
<SectionTitleLine
|
||||
:icon="mdiShieldEditOutline"
|
||||
title="User Administration"
|
||||
main
|
||||
/>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||
<CardBox is-form class="" @submit.prevent="adminUpdateUser">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="General Information"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<div class="grid md:grid-cols-2 gap-x-4 mb-6">
|
||||
<FormField label="Username">
|
||||
<FormControl
|
||||
v-model="newUser.name"
|
||||
:input-value="newUser.name"
|
||||
name="username"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label="E-mail"
|
||||
help="Used for password resetting and 2FA"
|
||||
>
|
||||
<FormControl
|
||||
v-model="newUser.email"
|
||||
:icon="mdiMail"
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="PIN" help="Used when logging into a game">
|
||||
<FormControl
|
||||
v-model="newUser.pin"
|
||||
:icon="mdiAsterisk"
|
||||
type="password"
|
||||
name="pin"
|
||||
:minlength="4"
|
||||
:maxlength="4"
|
||||
inputmode="numeric"
|
||||
pattern="\d{4}"
|
||||
@input="pinInput"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
label="Public Profile"
|
||||
help="Show this profile publicly. If disabled, only game profiles and scores will be visible."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newUser.public"
|
||||
name="public"
|
||||
:model-value="newUser.public"
|
||||
:input-value="newUser.public"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label="Ban Profile"
|
||||
help="Ban this profile. Locks out all arcades that this user manages."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newUser.banned"
|
||||
name="banned"
|
||||
:model-value="newUser.banned"
|
||||
:input-value="newUser.banned"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div
|
||||
v-if="JSON.stringify(userProfile) !== JSON.stringify(newUser)"
|
||||
class="space-x-2 mt-6 mb-4"
|
||||
>
|
||||
<BaseButton color="success" label="Save" type="submit" />
|
||||
<BaseButton
|
||||
color="warning"
|
||||
label="Revert"
|
||||
@click="loadUser()"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox is-form @submit.prevent="adminSubmitPassword()">
|
||||
<PillTag color="info" label="Change Password" class="mb-2" />
|
||||
<FormField label="New Password">
|
||||
<FormControl
|
||||
v-model="passwordForm.newPassword"
|
||||
:icon="mdiAsterisk"
|
||||
name="newPassword"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Confirm Password">
|
||||
<FormControl
|
||||
v-model="passwordForm.confirmPassword"
|
||||
:icon="mdiAsterisk"
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton type="submit" color="success" label="Update" />
|
||||
</template>
|
||||
</CardBox>
|
||||
<CardBox v-if="userProfile?.arcades?.length" class="mb-6">
|
||||
<PillTag color="info" label="Arcades" class="mb-2" />
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div
|
||||
v-for="arcade of userProfile.arcades"
|
||||
:key="arcade.id"
|
||||
class="bg-slate-800 p-4 rounded-xl"
|
||||
>
|
||||
<div class="md:flex w-full place-content-between">
|
||||
<div>
|
||||
<h1 class="text-md md:text-lg">{{ arcade.name }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex align-middle mt-2 md:mt-0 max-h-12">
|
||||
<BaseButton
|
||||
label="Open Arcade"
|
||||
color="info"
|
||||
@click="openArcade(arcade)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SectionTitleLine
|
||||
:icon="mdiChartTimelineVariant"
|
||||
title="Quick Stats"
|
||||
|
|
@ -264,7 +462,8 @@ const cardBoxes = ref([
|
|||
:key="profile.game"
|
||||
:game="profile.game"
|
||||
:value="profile.data.total_plays"
|
||||
profile-name=""
|
||||
:user-id="reqUserId"
|
||||
:profile-name="profile?.username"
|
||||
type="plays"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user