mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-09-13 03:35:18 -05:00
Add new cards, add new admin arcade features
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
VITE_APP_VERSION="3.0.17"
|
||||
VITE_APP_VERSION="3.0.18"
|
||||
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.17"
|
||||
VITE_APP_VERSION="3.0.18"
|
||||
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"
|
||||
|
||||
BIN
public/assets/border/lesbian.webp
Normal file
BIN
public/assets/border/lesbian.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/assets/border/neon.webp
Normal file
BIN
public/assets/border/neon.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
BIN
public/assets/card/lesbian.webp
Normal file
BIN
public/assets/card/lesbian.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/assets/card/nonbinary.webp
Normal file
BIN
public/assets/card/nonbinary.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -16,5 +16,6 @@
|
||||
"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.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."]
|
||||
"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."],
|
||||
"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."]
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup>
|
||||
import { mdiDownload, mdiChevronLeft, mdiChevronRight } from "@mdi/js";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import UserAvatar from "@/components/UserAvatar.vue";
|
||||
import DownloadJS from "downloadjs";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const $route = useRoute();
|
||||
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
const props = defineProps(["headers", "items"]);
|
||||
const props = defineProps(["headers", "items", "hasAvatar"]);
|
||||
const emits = defineEmits(["row-clicked"]);
|
||||
|
||||
const handleRowClick = (item) => {
|
||||
@@ -78,6 +79,23 @@ function downloadJSON() {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="props.hasAvatar" #item-username="{ username, data }">
|
||||
<div class="username-wrapper flex items-center gap-2">
|
||||
<div class="w-6">
|
||||
<UserAvatar
|
||||
:avatar="
|
||||
data?.discord
|
||||
? `https://cdn.discordapp.com/avatars/${data?.discord?.id}/${data?.discord?.avatar}`
|
||||
: null
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<span>{{ username }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
</EasyDataTable>
|
||||
<div class="p-2 w-full flex md:justify-end">
|
||||
<BaseButton
|
||||
|
||||
@@ -7,6 +7,8 @@ export class ProfileCustomizations {
|
||||
{ id: "gradient", label: "Pink/Purple Gradient" },
|
||||
{ id: "pride", label: "Pride Flag" },
|
||||
{ id: "trans", label: "Trans Flag" },
|
||||
{ id: "nonbinary", label: "Non-Binary Flag" },
|
||||
{ id: "lesbian", label: "Lesbian Flag" },
|
||||
{ id: "loveplus", label: "LovePlus" },
|
||||
{ id: "paint", label: "i eat paint" },
|
||||
];
|
||||
@@ -29,6 +31,7 @@ export class ProfileCustomizations {
|
||||
{ id: "pride", label: "Pride Flag" },
|
||||
{ id: "trans", label: "Trans Flag" },
|
||||
{ id: "nonbinary", label: "Non-Binary Flag" },
|
||||
{ id: "lesbian", label: "Lesbian Flag" },
|
||||
{ id: "leaves", label: "Leaves" },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -84,6 +84,38 @@ export async function APIAdminUpdateArcade(arcadeId, newArcade) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminAddArcadeOwner(arcadeId, ownerId) {
|
||||
try {
|
||||
const data = await mainStore.callApi(
|
||||
`/admin/arcade/${arcadeId}/owner`,
|
||||
"PUT",
|
||||
{ ownerId: ownerId }
|
||||
);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error updating arcade owner:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminRemoveArcadeOwner(arcadeId, ownerId) {
|
||||
try {
|
||||
const confirmed = window.confirm("Are you really?");
|
||||
if (confirmed) {
|
||||
const data = await mainStore.callApi(
|
||||
`/admin/arcade/${arcadeId}/owner`,
|
||||
"DELETE",
|
||||
{ ownerId: ownerId }
|
||||
);
|
||||
return data;
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.log("Error removing arcade owner:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminMaintenancePeriods() {
|
||||
try {
|
||||
const data = await mainStore.callApi(`/admin/maint`);
|
||||
@@ -124,9 +156,9 @@ export async function APIAdminCreateClient(newClient) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function APIAdminUsers() {
|
||||
export async function APIAdminUsers(noData = false) {
|
||||
try {
|
||||
const data = await mainStore.callApi(`/admin/users`);
|
||||
const data = await mainStore.callApi(`/admin/users?noData=${noData}`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log("Error loading users:", error);
|
||||
|
||||
@@ -35,16 +35,19 @@ const headers = [
|
||||
text: "Owners",
|
||||
value: "owners",
|
||||
width: 150,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: "PASELI Enabled",
|
||||
value: "data.paseli_enabled",
|
||||
width: 50,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: "Infinite PASELI",
|
||||
value: "data.paseli_infinite",
|
||||
width: 50,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
text: "Beta Enabled",
|
||||
|
||||
@@ -19,7 +19,7 @@ const headers = [
|
||||
{
|
||||
text: "User ID",
|
||||
value: "id",
|
||||
width: 50,
|
||||
width: 20,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
@@ -173,6 +173,7 @@ function filterUsers() {
|
||||
<GeneralTable
|
||||
:headers="headers"
|
||||
:items="userData"
|
||||
:has-avatar="true"
|
||||
@row-clicked="openUser"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import {
|
||||
mdiStore,
|
||||
mdiStoreCog,
|
||||
@@ -21,14 +21,22 @@ import FormControl from "@/components/FormControl.vue";
|
||||
import PillTag from "@/components/PillTag.vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
import BaseDivider from "@/components/BaseDivider.vue";
|
||||
import CardBoxWidget from "@/components/CardBoxWidget.vue";
|
||||
import UserAvatar from "@/components/UserAvatar.vue";
|
||||
import { getNestedValue, setNestedValue } from "@/constants/values";
|
||||
import {
|
||||
APIGetArcade,
|
||||
APIUpdateArcade,
|
||||
APIGetArcadeVPN,
|
||||
} from "@/stores/api/arcade";
|
||||
import { checkArcadeName, APIAdminUpdateArcade } from "@/stores/api/admin";
|
||||
import {
|
||||
checkArcadeName,
|
||||
APIAdminUpdateArcade,
|
||||
APIAdminUsers,
|
||||
APIAdminAddArcadeOwner,
|
||||
APIAdminRemoveArcadeOwner,
|
||||
} from "@/stores/api/admin";
|
||||
import { useMainStore } from "@/stores/main";
|
||||
|
||||
const mainStore = useMainStore();
|
||||
@@ -38,6 +46,8 @@ const arcadeData = ref(null);
|
||||
const loading = ref(true);
|
||||
const arcadeCheck = ref(null);
|
||||
const newArcade = ref(null);
|
||||
const newManager = ref(null);
|
||||
const userData = ref(null);
|
||||
|
||||
const $route = useRoute();
|
||||
const arcadeId = parseInt($route.params.id);
|
||||
@@ -89,6 +99,36 @@ async function loadArcade() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
if (mainStore.userAdmin) {
|
||||
try {
|
||||
const data = await APIAdminUsers(true);
|
||||
const filteredData = [];
|
||||
|
||||
for (const user of data) {
|
||||
if (user.username != undefined) {
|
||||
filteredData.push({
|
||||
id: user.id,
|
||||
label: user.username,
|
||||
});
|
||||
}
|
||||
}
|
||||
filteredData.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
userData.value = filteredData;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch admin user data:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => mainStore.userAdmin,
|
||||
() => {
|
||||
loadUsers();
|
||||
}
|
||||
);
|
||||
|
||||
async function updateArcade() {
|
||||
const response = await APIUpdateArcade(arcadeId, optionForm.value);
|
||||
|
||||
@@ -99,6 +139,7 @@ async function updateArcade() {
|
||||
|
||||
onMounted(() => {
|
||||
loadArcade();
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
function formatName(inputString, replaceWith = "NA_") {
|
||||
@@ -156,6 +197,31 @@ async function adminUpdateArcade() {
|
||||
await loadArcade();
|
||||
}
|
||||
}
|
||||
|
||||
async function addManager() {
|
||||
if (newManager.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await APIAdminAddArcadeOwner(arcadeId, newManager.value);
|
||||
|
||||
if (response.status != "error") {
|
||||
newManager.value = null;
|
||||
loadArcade();
|
||||
}
|
||||
}
|
||||
|
||||
async function removeManager(ownerId) {
|
||||
if (!ownerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await APIAdminRemoveArcadeOwner(arcadeId, ownerId);
|
||||
|
||||
if (response.status != "error") {
|
||||
loadArcade();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -170,67 +236,136 @@ async function adminUpdateArcade() {
|
||||
title="Arcade Administration"
|
||||
main
|
||||
/>
|
||||
<CardBox is-form class="mb-6" @submit.prevent="adminUpdateArcade">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="General Information"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<FormField label="Arcade Name">
|
||||
<FormControl
|
||||
v-model="newArcade.name"
|
||||
:input-value="newArcade.name"
|
||||
name="arcadeName"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div class="mb-4 flex gap-2 items-stretch">
|
||||
<BaseButton
|
||||
color="info"
|
||||
label="Check Name"
|
||||
@click="checkName()"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-if="arcadeCheck == true"
|
||||
:path="mdiCheckOutline"
|
||||
color="text-green-400"
|
||||
size="25"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else-if="arcadeCheck == false"
|
||||
:path="mdiCloseOutline"
|
||||
color="text-red-400"
|
||||
size="25"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField label="Description">
|
||||
<FormControl
|
||||
v-model="newArcade.description"
|
||||
name="description"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Beta Enabled">
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.data.is_beta"
|
||||
name="beta"
|
||||
:model-value="newArcade.data.is_beta"
|
||||
:input-value="newArcade.data.is_beta"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<div
|
||||
v-if="JSON.stringify(arcadeData) !== JSON.stringify(newArcade)"
|
||||
class="space-x-2 mt-6"
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<CardBox
|
||||
is-form
|
||||
class="lg:mb-6"
|
||||
@submit.prevent="adminUpdateArcade"
|
||||
>
|
||||
<BaseButton color="success" label="Save" type="submit" />
|
||||
<BaseButton color="danger" label="Revert" @click="loadArcade()" />
|
||||
</div>
|
||||
</CardBox>
|
||||
<PillTag
|
||||
color="info"
|
||||
label="General Information"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<FormField label="Arcade Name">
|
||||
<FormControl
|
||||
v-model="newArcade.name"
|
||||
:input-value="newArcade.name"
|
||||
name="arcadeName"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<div class="mb-4 flex gap-2 items-stretch">
|
||||
<BaseButton
|
||||
color="info"
|
||||
label="Check Name"
|
||||
@click="checkName()"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-if="arcadeCheck == true"
|
||||
:path="mdiCheckOutline"
|
||||
color="text-green-400"
|
||||
size="25"
|
||||
/>
|
||||
<BaseIcon
|
||||
v-else-if="arcadeCheck == false"
|
||||
:path="mdiCloseOutline"
|
||||
color="text-red-400"
|
||||
size="25"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FormField label="Description">
|
||||
<FormControl
|
||||
v-model="newArcade.description"
|
||||
name="description"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="Beta Enabled">
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.data.is_beta"
|
||||
name="beta"
|
||||
:model-value="newArcade.data.is_beta"
|
||||
:input-value="newArcade.data.is_beta"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<div
|
||||
v-if="JSON.stringify(arcadeData) !== JSON.stringify(newArcade)"
|
||||
class="space-x-2 mt-6"
|
||||
>
|
||||
<BaseButton color="success" label="Save" type="submit" />
|
||||
<BaseButton
|
||||
color="danger"
|
||||
label="Revert"
|
||||
@click="loadArcade()"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox class="mb-6">
|
||||
<div class="mb-4">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="Add Manager"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<form class="h-full" @submit.prevent="addManager">
|
||||
<FormField label="Username">
|
||||
<FormControl
|
||||
v-model="newManager"
|
||||
name="username"
|
||||
:options="userData"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseButton
|
||||
color="success"
|
||||
type="submit"
|
||||
label="Add Manager"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<div class="mb-4">
|
||||
<PillTag color="warning" label="Managers" class="mb-4" />
|
||||
<div class="grid gap-4">
|
||||
<CardBox
|
||||
v-for="owner of arcadeData.owners"
|
||||
:key="owner"
|
||||
color-prop="bg-slate-800 dark:bg-slate-800"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="m-[-5px] md:flex items-center gap-2">
|
||||
<div class="w-8 md:w-12">
|
||||
<UserAvatar
|
||||
:username="owner.username"
|
||||
:avatar="owner.avatar"
|
||||
/>
|
||||
</div>
|
||||
<h1 class="text-lg lg:text-xl font-bold">
|
||||
{{ owner.username }}
|
||||
</h1>
|
||||
</div>
|
||||
<BaseButton
|
||||
color="danger"
|
||||
label="Remove"
|
||||
@click="removeManager(owner.id)"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SectionTitleLine :icon="mdiStoreCog" title="Arcade Management" main />
|
||||
|
||||
@@ -42,11 +42,13 @@ const headers = [
|
||||
{
|
||||
text: "PCBID",
|
||||
value: "pcbId",
|
||||
sortable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
text: "Name",
|
||||
value: "description",
|
||||
sortable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
@@ -55,6 +57,12 @@ const headers = [
|
||||
sortable: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
text: "Arcade Machine",
|
||||
value: "data.cabinet",
|
||||
sortable: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
text: "OTA",
|
||||
value: "ota",
|
||||
|
||||
Reference in New Issue
Block a user