mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-04-24 06:48:56 -05:00
Lay groundwork for public profiles, clean up user loading
Some checks failed
Build / build (push) Has been cancelled
Some checks failed
Build / build (push) Has been cancelled
This commit is contained in:
parent
ffe3d7133c
commit
4632b9cd19
|
|
@ -1,4 +1,4 @@
|
|||
VITE_APP_VERSION="3.0.16"
|
||||
VITE_APP_VERSION="3.0.17"
|
||||
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.16"
|
||||
VITE_APP_VERSION="3.0.17"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -15,5 +15,6 @@
|
|||
"3.0.13": ["- (Feature) Add new game event settings to beatmaniaIIDX EPOLIS."],
|
||||
"3.0.14": ["- (Minor) Fix footer buttons on tables, add generic export table button for every table."],
|
||||
"3.0.15": ["- (Minor) Add beta support for Pinky Crush, clean up gameDB a little.", "- (Optimization) Convert dashboard to a more modular backend.", "- (Bugfix) Fix warnings on auth pages."],
|
||||
"3.0.16": ["- (Major) Add multiple new admin features for network management.", "- (Optimization) Optimize backend for arcade operations.", "- (Bugfix) Fix possible backend issues with better type enforcing.", "- (Bugfix) Fix event data and game data for IIDX Pinky Crush.", "- (Minor) Add assets for Nostalgia."]
|
||||
"3.0.16": ["- (Major) Add multiple new admin features for network management.", "- (Optimization) Optimize backend for arcade operations.", "- (Bugfix) Fix possible backend issues with better type enforcing.", "- (Bugfix) Fix event data and game data for IIDX Pinky Crush.", "- (Minor) Add assets for Nostalgia."],
|
||||
"3.0.17": ["- (Major) Add initial public profile support.", "- (Minor) Lay groundwork for public profile page", "- (Optimization) Clean up admin pages", "- (Bugfix) Clean up random 500 errors."]
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useMainStore } from "@/stores/main";
|
||||
import { RoleConstants } from "@/constants/discordRoles";
|
||||
import {
|
||||
mdiSecurity,
|
||||
mdiTestTube,
|
||||
mdiAccountStar,
|
||||
mdiCodeBraces,
|
||||
mdiAccountOff,
|
||||
mdiFlowerPoppy,
|
||||
mdiSharkFinOutline,
|
||||
|
|
@ -15,15 +15,20 @@ import {
|
|||
mdiHeartMultipleOutline,
|
||||
mdiCheckDecagramOutline,
|
||||
mdiLinkBoxVariantOutline,
|
||||
// mdiAccountCheck,
|
||||
mdiAccountCheck,
|
||||
} from "@mdi/js";
|
||||
import BaseLevel from "@/components/BaseLevel.vue";
|
||||
import UserAvatarCurrentUser from "@/components/UserAvatarCurrentUser.vue";
|
||||
import UserAvatar from "@/components/UserAvatar.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
import PillTag from "@/components/PillTag.vue";
|
||||
const ASSET_PATH = import.meta.env.VITE_ASSET_PATH;
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
overrideProfile: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
useSmall: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -38,14 +43,46 @@ defineProps({
|
|||
|
||||
import { GetRandomMessage } from "@/constants";
|
||||
|
||||
const mainStore = useMainStore();
|
||||
const cardData = ref({
|
||||
userId: null,
|
||||
userName: "",
|
||||
userAvatar: "",
|
||||
userAdmin: false,
|
||||
userPublic: false,
|
||||
discordRoles: null,
|
||||
userCustomize: null,
|
||||
});
|
||||
|
||||
if (props.overrideProfile) {
|
||||
const overrideProfile = props.overrideProfile;
|
||||
cardData.value = {
|
||||
userId: overrideProfile.id,
|
||||
userName: overrideProfile.name,
|
||||
userAvatar: overrideProfile.avatar,
|
||||
userAdmin: overrideProfile.admin,
|
||||
userPublic: overrideProfile.public,
|
||||
discordRoles: overrideProfile.discordRoles,
|
||||
userCustomize: overrideProfile.customize,
|
||||
};
|
||||
} else {
|
||||
const mainStore = useMainStore();
|
||||
cardData.value = {
|
||||
userId: mainStore.userId,
|
||||
userName: mainStore.userName,
|
||||
userAvatar: mainStore.userAvatar,
|
||||
userAdmin: mainStore.userAdmin,
|
||||
userPublic: mainStore.userPublic,
|
||||
discordRoles: mainStore.discordRoles,
|
||||
userCustomize: mainStore.userCustomize,
|
||||
};
|
||||
}
|
||||
|
||||
const greeting = GetRandomMessage();
|
||||
|
||||
function getCardStyle() {
|
||||
return `
|
||||
background-image: url('${ASSET_PATH}/card/${
|
||||
mainStore.userCustomize?.card ?? "time"
|
||||
cardData.value.userCustomize?.card ?? "time"
|
||||
}.webp');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
|
@ -59,23 +96,26 @@ function getCardStyle() {
|
|||
type="justify-around lg:justify-center md:space-x-4 lg:space-x-0"
|
||||
class="bg-white dark:bg-slate-900/90 rounded-2xl p-3"
|
||||
>
|
||||
<UserAvatarCurrentUser
|
||||
<UserAvatar
|
||||
class="w-28 md:w-30 lg:w-[128px] lg:mx-12 lg:m-2"
|
||||
:username="cardData.userName"
|
||||
:avatar="cardData.userAvatar"
|
||||
:border="cardData.userCustomize?.border ?? null"
|
||||
/>
|
||||
<div class="space-y-3 text-center md:text-left lg:mx-12">
|
||||
<div class="space-y-2 md:space-y-0">
|
||||
<h1
|
||||
v-if="!useSmall && !mainStore.userCustomize.disableGreeting"
|
||||
v-if="!useSmall && !cardData.userCustomize?.disableGreeting"
|
||||
class="text-2xl md:text-xl lg:text-2xl"
|
||||
>
|
||||
{{ greeting.header[0] }}<b>{{ mainStore.userName }} </b
|
||||
{{ greeting.header[0] }}<b>{{ cardData.userName }} </b
|
||||
>{{ greeting.header[1] }}
|
||||
</h1>
|
||||
<h1
|
||||
v-if="useSmall || mainStore.userCustomize.disableGreeting"
|
||||
v-if="useSmall || cardData.userCustomize?.disableGreeting"
|
||||
class="text-3xl md:text-4xl"
|
||||
>
|
||||
<b>{{ mainStore.userName }}</b>
|
||||
<b>{{ cardData.userName }}</b>
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
|
|
@ -83,90 +123,91 @@ function getCardStyle() {
|
|||
class="flex flex-wrap gap-2 md:place-content-start place-content-center px-5 sm:px-0 py-2 sm:py-0 md:max-w-[400px]"
|
||||
>
|
||||
<PillTag
|
||||
v-if="mainStore.userAdmin"
|
||||
v-if="cardData.userAdmin"
|
||||
label="System Admin"
|
||||
color="danger"
|
||||
:icon="mdiSecurity"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.userId < 300"
|
||||
v-if="cardData.userId < 300"
|
||||
label="Veteran"
|
||||
color="success"
|
||||
:icon="mdiAccountStar"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.userData.dev"
|
||||
label="Active Dev"
|
||||
color="info"
|
||||
:icon="mdiCodeBraces"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="!cardData.userPublic"
|
||||
label="Private Profile"
|
||||
color="info"
|
||||
:icon="mdiAccountOff"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.PLAYER)"
|
||||
v-if="cardData.userPublic"
|
||||
label="Public Profile"
|
||||
color="success"
|
||||
:icon="mdiAccountCheck"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.PLAYER)"
|
||||
label="Verified"
|
||||
color="success"
|
||||
:icon="mdiCheckDecagramOutline"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.JACKASS)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.JACKASS)"
|
||||
label="Jackass"
|
||||
color="slight_danger"
|
||||
:icon="mdiHeartMultipleOutline"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.DEVELOPER)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.DEVELOPER)"
|
||||
label="Developer"
|
||||
color="success"
|
||||
:icon="mdiAccountTie"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.MODERATOR)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.MODERATOR)"
|
||||
label="Moderator"
|
||||
color="slight_danger"
|
||||
:icon="mdiAccountTieHat"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.BETA_TESTER)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.BETA_TESTER)"
|
||||
label="Beta Tester"
|
||||
color="warning"
|
||||
:icon="mdiTestTube"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.DONOR)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.DONOR)"
|
||||
label="Donor"
|
||||
color="gold"
|
||||
:icon="mdiHandCoinOutline"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.BLAHAJ)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.BLAHAJ)"
|
||||
label="Blåhaj"
|
||||
color="info"
|
||||
:icon="mdiSharkFinOutline"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.discordRoles?.includes(RoleConstants.RHYTHM_RIOT)"
|
||||
v-if="cardData.discordRoles?.includes(RoleConstants.RHYTHM_RIOT)"
|
||||
label="Rhythm Riot"
|
||||
color="sakura"
|
||||
:icon="mdiFlowerPoppy"
|
||||
small
|
||||
/>
|
||||
<PillTag
|
||||
v-if="mainStore.userCustomize.shrimpLinks"
|
||||
v-if="cardData.userCustomize?.shrimpLinks"
|
||||
label="Shrimp Links"
|
||||
color="sakura"
|
||||
:icon="mdiLinkBoxVariantOutline"
|
||||
|
|
@ -180,7 +221,7 @@ function getCardStyle() {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!useSmall && !mainStore.userCustomize.disableGreeting"
|
||||
v-if="!useSmall && !cardData.userCustomize.disableGreeting"
|
||||
class="text-center md:text-right"
|
||||
>
|
||||
<p class="text-xl md:text-lg lg:text-2lg">{{ greeting.comment }}</p>
|
||||
|
|
|
|||
|
|
@ -158,10 +158,6 @@ const menuAside = computed(() => {
|
|||
label: "Arcades",
|
||||
to: "/admin/arcades",
|
||||
},
|
||||
{
|
||||
label: "Cards",
|
||||
to: "/admin/cards",
|
||||
},
|
||||
{
|
||||
label: "Users",
|
||||
to: "/admin/users",
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ const routes = [
|
|||
},
|
||||
{
|
||||
meta: {
|
||||
title: "View Profile",
|
||||
title: "View User",
|
||||
},
|
||||
path: "/profiles/:id",
|
||||
name: "profile_viewer",
|
||||
|
|
@ -173,14 +173,6 @@ const routes = [
|
|||
name: "admin_arcades",
|
||||
component: () => import("@/views/Admin/ArcadesView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Cards",
|
||||
},
|
||||
path: "/admin/cards",
|
||||
name: "admin_cards",
|
||||
component: () => import("@/views/Admin/CardsView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Users",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,33 @@
|
|||
import { useMainStore } from "@/stores/main";
|
||||
|
||||
export async function APIGetUser(userId) {
|
||||
const mainStore = useMainStore();
|
||||
|
||||
try {
|
||||
const user = await mainStore.callApi(`/user?userId=${userId}`);
|
||||
const userData = user.data;
|
||||
const data = {
|
||||
id: userData.id,
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
avatar: userData.avatar,
|
||||
admin: userData.admin,
|
||||
data: userData.data,
|
||||
discordRoles: userData.discordRoles,
|
||||
cardStyle: "time",
|
||||
profiles: userData.profiles,
|
||||
arcades: userData.arcades,
|
||||
customize: userData.data?.customize,
|
||||
userScoreStats: userData.scoreStats,
|
||||
public: userData.public,
|
||||
};
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error loading user:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIEmailAuth(email) {
|
||||
const mainStore = useMainStore();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export const useMainStore = defineStore("main", {
|
|||
profiles: {},
|
||||
userCustomize: {},
|
||||
userScoreStats: {},
|
||||
userPublic: false,
|
||||
|
||||
/* Field focus with ctrl+k (to register only once) */
|
||||
isFieldFocusRegistered: false,
|
||||
|
|
@ -59,6 +60,9 @@ export const useMainStore = defineStore("main", {
|
|||
if (payload.admin) {
|
||||
this.userAdmin = payload.admin;
|
||||
}
|
||||
if (payload.public) {
|
||||
this.userPublic = payload.public;
|
||||
}
|
||||
if (payload.data) {
|
||||
this.userData = payload.data;
|
||||
}
|
||||
|
|
@ -258,8 +262,8 @@ export const useMainStore = defineStore("main", {
|
|||
if (validSession && validSession.activeSession && !this.userLoaded) {
|
||||
const userId = validSession.userId;
|
||||
if (userId) {
|
||||
const response = await this.callApi(`/user?userId=${userId}`);
|
||||
var user = response.user;
|
||||
const response = await this.callApi(`/user`);
|
||||
var user = response.data;
|
||||
this.setUser({
|
||||
id: userId,
|
||||
name: user.name,
|
||||
|
|
@ -268,11 +272,11 @@ export const useMainStore = defineStore("main", {
|
|||
admin: user.admin,
|
||||
data: user.data,
|
||||
discordRoles: user.discordRoles,
|
||||
cardStyle: "time",
|
||||
profiles: user.profiles,
|
||||
arcades: user.arcades,
|
||||
customize: user.data?.customize,
|
||||
userScoreStats: user.scoreStats,
|
||||
public: user.public,
|
||||
});
|
||||
this.userLoaded = true;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } from "@mdi/js";
|
||||
import SectionMain from "@/components/SectionMain.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
import CardBoxWidget from "@/components/CardBoxWidget.vue";
|
||||
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLine from "@/components/SectionTitleLine.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
main
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
|
||||
<CardBoxWidget :number="1" label="User Account(s)" />
|
||||
<CardBoxWidget :number="2" label="Registered Arcades" />
|
||||
<CardBoxWidget :number="3" label="Published Scores" />
|
||||
</div>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
|
@ -78,7 +78,7 @@ async function loadData() {
|
|||
|
||||
const openUser = (item) => {
|
||||
const userId = item.id;
|
||||
$router.push(`/user/${userId}`);
|
||||
$router.push(`/profiles/${userId}`);
|
||||
};
|
||||
|
||||
const filterForm = reactive({
|
||||
|
|
@ -173,7 +173,7 @@ function filterUsers() {
|
|||
<GeneralTable
|
||||
:headers="headers"
|
||||
:items="userData"
|
||||
@row-clicked="openUser(user)"
|
||||
@row-clicked="openUser"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CardBox from "@/components/CardBox.vue";
|
|||
import BaseDivider from "@/components/BaseDivider.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 BaseIcon from "@/components/BaseIcon.vue";
|
||||
import UserCard from "@/components/UserCard.vue";
|
||||
|
|
@ -23,12 +24,14 @@ const currentProfile = reactive({
|
|||
username: JSON.parse(JSON.stringify(mainStore.userName)),
|
||||
email: JSON.parse(JSON.stringify(mainStore.userEmail)),
|
||||
pin: null,
|
||||
public: mainStore.userPublic ?? false,
|
||||
});
|
||||
|
||||
const profileForm = reactive({
|
||||
username: JSON.parse(JSON.stringify(mainStore.userName)),
|
||||
email: JSON.parse(JSON.stringify(mainStore.userEmail)),
|
||||
pin: null,
|
||||
public: mainStore.userPublic ?? false,
|
||||
});
|
||||
|
||||
const passwordForm = reactive({
|
||||
|
|
@ -56,6 +59,14 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => mainStore.userPublic,
|
||||
(newValue) => {
|
||||
profileForm.public = newValue;
|
||||
currentProfile.public = newValue;
|
||||
}
|
||||
);
|
||||
|
||||
async function submitProfile() {
|
||||
profileLoading.value = true;
|
||||
const response = await mainStore.putUser(profileForm);
|
||||
|
|
@ -139,6 +150,19 @@ function userChanged(oldProfile, newProfile) {
|
|||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField
|
||||
label="Public Profile"
|
||||
help="Show my profile publicly. If disabled, only game profiles and scores will be visible."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="profileForm.public"
|
||||
name="public"
|
||||
:model-value="profileForm.public"
|
||||
:input-value="profileForm.public"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<template #footer>
|
||||
<BaseButton
|
||||
v-if="userChanged(currentProfile, profileForm)"
|
||||
|
|
|
|||
|
|
@ -1,70 +1,284 @@
|
|||
<script setup>
|
||||
import { computed, ref, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiAccountPlusOutline,
|
||||
mdiAccountMinusOutline,
|
||||
mdiGamepad,
|
||||
mdiChartTimelineVariant,
|
||||
mdiCounter,
|
||||
mdiGamepadOutline,
|
||||
mdiFire,
|
||||
mdiTrendingUp,
|
||||
} from "@mdi/js";
|
||||
import SectionMain from "@/components/SectionMain.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
import CardBoxWidget from "@/components/CardBoxWidget.vue";
|
||||
import CardBoxGameStat from "@/components/CardBoxGameStat.vue";
|
||||
import UserCard from "@/components/UserCard.vue";
|
||||
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLine from "@/components/SectionTitleLine.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import { GameConstants } from "@/constants";
|
||||
import LineChart from "@/components/Charts/LineChart.vue";
|
||||
import { getGameInfo } from "@/constants";
|
||||
import { APIGetUser } from "@/stores/api/account";
|
||||
|
||||
const $route = useRoute();
|
||||
const reqUserId = $route.params.id;
|
||||
const userProfile = ref({});
|
||||
|
||||
async function loadUser() {
|
||||
try {
|
||||
userProfile.value = {};
|
||||
var data = await APIGetUser(reqUserId);
|
||||
if (!data.name) {
|
||||
data.name = "Unclaimed Account";
|
||||
}
|
||||
userProfile.value = data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch user profile data:", error);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadUser();
|
||||
console.log(userProfile.value);
|
||||
});
|
||||
|
||||
const userProfiles = computed(() => userProfile.value.userProfiles);
|
||||
const userScoreStats = computed(() => userProfile.value.userScoreStats);
|
||||
|
||||
const cumulativePlays = computed(() => {
|
||||
return userProfiles.value?.reduce(
|
||||
(total, user) => total + user.data.total_plays,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
const uniqueProfiles = computed(() => {
|
||||
return userProfiles.value?.length;
|
||||
});
|
||||
|
||||
const longestStreak = computed(() => {
|
||||
const groupByDay = (timestamps) => {
|
||||
const dayMap = {};
|
||||
timestamps.forEach((timestamp) => {
|
||||
const day = new Date(timestamp * 1000).toISOString().split("T")[0];
|
||||
dayMap[day] = (dayMap[day] || 0) + 1;
|
||||
});
|
||||
return dayMap;
|
||||
};
|
||||
|
||||
var maxStreak = 0;
|
||||
|
||||
userProfiles.value?.forEach((user) => {
|
||||
const arcadeHistory = user.data?.arcade_history
|
||||
? user.data?.arcade_history
|
||||
: {};
|
||||
const allTimestamps = [];
|
||||
|
||||
Object.values(arcadeHistory).forEach((machines) => {
|
||||
Object.values(machines).forEach((timestamps) => {
|
||||
allTimestamps.push(...timestamps);
|
||||
});
|
||||
});
|
||||
|
||||
const playsByDay = groupByDay(allTimestamps);
|
||||
|
||||
const longestStreakForUser = Math.max(...Object.values(playsByDay));
|
||||
maxStreak = Math.max(maxStreak, longestStreakForUser);
|
||||
});
|
||||
|
||||
return maxStreak;
|
||||
});
|
||||
|
||||
function filterUserProfiles(userProfiles) {
|
||||
if (!userProfiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
var filteredProfiles = [];
|
||||
for (const profile of userProfiles) {
|
||||
const game = getGameInfo(profile.game);
|
||||
if (game && !game.skip) {
|
||||
filteredProfiles.push(profile);
|
||||
}
|
||||
}
|
||||
|
||||
filteredProfiles.sort(function (x, y) {
|
||||
return y.data.last_play_timestamp - x.data.last_play_timestamp;
|
||||
});
|
||||
|
||||
return filteredProfiles;
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
const totalAttempts = computed(() => {
|
||||
return userScoreStats.value?.attempts?.length || 0;
|
||||
});
|
||||
|
||||
const totalRecords = computed(() => {
|
||||
return userScoreStats.value?.records?.length || 0;
|
||||
});
|
||||
|
||||
const todayAttempts = computed(() => {
|
||||
return (
|
||||
userScoreStats.value?.attempts?.filter((a) => {
|
||||
const attemptDate = new Date(a.timestamp * 1000)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
return attemptDate === today;
|
||||
}).length || 0
|
||||
);
|
||||
});
|
||||
|
||||
const todayRecords = computed(() => {
|
||||
return (
|
||||
userScoreStats.value?.records?.filter((r) => {
|
||||
const recordDate = new Date(r.timestamp * 1000)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
return recordDate === today;
|
||||
}).length || 0
|
||||
);
|
||||
});
|
||||
|
||||
const todayPlays = computed(() => {
|
||||
const todayStr = new Date().toISOString().split("T")[0];
|
||||
let total = 0;
|
||||
|
||||
userProfiles.value?.forEach((profile) => {
|
||||
const arcadeHistory = profile.data?.arcade_history || {};
|
||||
|
||||
Object.values(arcadeHistory).forEach((machines) => {
|
||||
Object.values(machines).forEach((timestamps) => {
|
||||
timestamps.forEach((ts) => {
|
||||
const dateStr = new Date(ts * 1000).toISOString().split("T")[0];
|
||||
if (dateStr === todayStr) {
|
||||
total++;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return total;
|
||||
});
|
||||
|
||||
const cardBoxes = ref([
|
||||
{
|
||||
label: "Cumulative Plays",
|
||||
icon: mdiCounter,
|
||||
iconColor: "text-emerald-600",
|
||||
suffix: "play",
|
||||
number: cumulativePlays,
|
||||
},
|
||||
{
|
||||
label: "Games Played",
|
||||
icon: mdiGamepadOutline,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "game",
|
||||
number: uniqueProfiles,
|
||||
},
|
||||
{
|
||||
label: "Plays Today",
|
||||
icon: mdiCounter,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "play",
|
||||
number: todayPlays,
|
||||
},
|
||||
{
|
||||
label: "Longest Play Streak",
|
||||
icon: mdiFire,
|
||||
iconColor: "text-red-500",
|
||||
suffix: "play",
|
||||
number: longestStreak,
|
||||
},
|
||||
{
|
||||
label: "Total Records",
|
||||
icon: mdiGamepadOutline,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "record",
|
||||
number: totalRecords,
|
||||
},
|
||||
{
|
||||
label: "Total Attempts",
|
||||
icon: mdiGamepadOutline,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "attempt",
|
||||
number: totalAttempts,
|
||||
},
|
||||
{
|
||||
label: "Records Today",
|
||||
icon: mdiGamepadOutline,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "record",
|
||||
number: todayRecords,
|
||||
},
|
||||
{
|
||||
label: "Attempts Today",
|
||||
icon: mdiGamepadOutline,
|
||||
iconColor: "text-sky-300",
|
||||
suffix: "attempt",
|
||||
number: todayAttempts,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine :icon="mdiAccount" title="Trmazi's Profile" main />
|
||||
<UserCard class="mb-6" use-small />
|
||||
<template v-if="userProfile != null">
|
||||
<SectionTitleLine
|
||||
:icon="mdiAccount"
|
||||
:title="`${userProfile.name}'s Profile`"
|
||||
main
|
||||
/>
|
||||
<UserCard class="mb-6" :override-profile="userProfile" use-small />
|
||||
|
||||
<CardBox is-form class="mb-6">
|
||||
<div class="flex gap-4">
|
||||
<BaseButton
|
||||
color="success"
|
||||
label="Add Friend"
|
||||
:icon="mdiAccountPlusOutline"
|
||||
/>
|
||||
<BaseButton
|
||||
color="danger"
|
||||
label="Remove Friend"
|
||||
:icon="mdiAccountMinusOutline"
|
||||
<SectionTitleLine
|
||||
:icon="mdiChartTimelineVariant"
|
||||
title="Quick Stats"
|
||||
main
|
||||
/>
|
||||
<div
|
||||
class="grid grid-cols-2 sm:grid-cols-3 gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 3xl:grid-cols-5 mb-6"
|
||||
>
|
||||
<template v-for="box of cardBoxes" :key="box.label">
|
||||
<CardBoxWidget
|
||||
v-if="box.number"
|
||||
:icon="box.icon"
|
||||
:number="box.number"
|
||||
:label="box.label"
|
||||
:suffix="`${box.suffix}${box.number == 1 ? '' : 's'}`"
|
||||
:icon-color="box.iconColor"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<SectionTitleLine :icon="mdiGamepad" title="Showcase" main />
|
||||
<div
|
||||
class="grid grid-flow-row auto-rows-auto grid-cols-2 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-3 3xl:grid-cols-4 4xl:grid-cols-6 gap-5 mb-5"
|
||||
>
|
||||
<CardBoxGameStat
|
||||
v-for="profile of filterUserProfiles(userProfiles)"
|
||||
:key="profile.game"
|
||||
:game="profile.game"
|
||||
:value="profile.data.total_plays"
|
||||
profile-name=""
|
||||
type="plays"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<SectionTitleLine :icon="mdiGamepad" title="Game Stats" main />
|
||||
<div
|
||||
class="grid grid-flow-row auto-rows-auto grid-cols-1 md:grid-cols-2 gap-5 mb-5"
|
||||
>
|
||||
<CardBoxGameStat
|
||||
:game="GameConstants.DDR"
|
||||
value="#10 out of 132"
|
||||
profile-name="DJ. TRMAZI"
|
||||
type="ranking"
|
||||
/>
|
||||
<CardBoxGameStat
|
||||
:game="GameConstants.POPN_MUSIC"
|
||||
:value="300"
|
||||
profile-name="TRMAZI"
|
||||
type="plays"
|
||||
/>
|
||||
<CardBoxGameStat
|
||||
:game="GameConstants.JUBEAT"
|
||||
:value="392"
|
||||
profile-name="TRMAZI"
|
||||
type="scores"
|
||||
/>
|
||||
<CardBoxGameStat
|
||||
:game="GameConstants.SDVX"
|
||||
value="#15 out of 200"
|
||||
profile-name="TRMAZI"
|
||||
type="ranking"
|
||||
/>
|
||||
</div>
|
||||
<SectionTitleLine :icon="mdiTrendingUp" title="Play Trends" main />
|
||||
<CardBox class="mb-6">
|
||||
<div v-if="userProfiles">
|
||||
<LineChart
|
||||
:data="generateChartData(userProfiles, userScoreStats)"
|
||||
class="h-96"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</template>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user