Shop Update
This commit is contained in:
@@ -6,28 +6,40 @@ import { useDevice } from "@/lib/context/DeviceContext";
|
|||||||
function Mobile() {
|
function Mobile() {
|
||||||
const { isAuthenticated, session } = useDevice();
|
const { isAuthenticated, session } = useDevice();
|
||||||
const [bio, setBio] = useState(session?.bio || "");
|
const [bio, setBio] = useState(session?.bio || "");
|
||||||
|
const [username, setUsername] = useState(session?.username || "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
setBio(session.bio || "");
|
setBio(session.bio || "");
|
||||||
|
setUsername(session.username || "");
|
||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (bio.length > 0) {
|
if (bio.length > 0 || username.length > 0) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("bio", bio);
|
if (bio.length > 0) formData.append("bio", bio);
|
||||||
|
if (username.length > 0) formData.append("username", username);
|
||||||
|
|
||||||
fetch("/api/me", {
|
fetch("/api/me", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData, // Automatically sets Content-Type to multipart/form-data
|
body: formData, // Automatically sets Content-Type to multipart/form-data
|
||||||
});
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
// Show a success message
|
.then((data) => {
|
||||||
alert("Bio saved!");
|
if (data.message === "User updated successfully") {
|
||||||
|
alert("Profile updated successfully!");
|
||||||
|
} else {
|
||||||
|
alert("Failed to update profile.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Error updating profile:", err);
|
||||||
|
alert("An error occurred while updating your profile.");
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
alert("Please enter a bio.");
|
alert("Please enter a bio or username.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +47,7 @@ function Mobile() {
|
|||||||
<div className="px-6 py-10 max-w-full lg:max-w-1/2 mx-auto font-sans text-neutral-100">
|
<div className="px-6 py-10 max-w-full lg:max-w-1/2 mx-auto font-sans text-neutral-100">
|
||||||
<div className="bg-[color:var(--color-surface-600)]/70 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
<div className="bg-[color:var(--color-surface-600)]/70 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold tracking-[-.01em] text-center">
|
<h1 className="text-2xl sm:text-3xl font-bold tracking-[-.01em] text-center">
|
||||||
Hi, {session?.username || ""}!!
|
Hi, {username || ""}!!
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div className="flex flex-col items-center mt-6 my-4">
|
<div className="flex flex-col items-center mt-6 my-4">
|
||||||
@@ -46,12 +58,19 @@ function Mobile() {
|
|||||||
className="w-42 h-42 rounded-full object-cover bg-surface-700"
|
className="w-42 h-42 rounded-full object-cover bg-surface-700"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* {uploadMessage && (
|
|
||||||
<p className="text-sm text-gray-400 mt-2">{uploadMessage}</p>
|
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="mb-6 space-y-2">
|
<form onSubmit={handleSubmit} className="mb-6 space-y-4">
|
||||||
|
{/* Username Input */}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full p-2 rounded bg-neutral-800 text-white"
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
value={username || ""}
|
||||||
|
placeholder="Update your username..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Bio Input */}
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full p-2 rounded bg-neutral-800 text-white"
|
className="w-full p-2 rounded bg-neutral-800 text-white"
|
||||||
onChange={(e) => setBio(e.target.value)}
|
onChange={(e) => setBio(e.target.value)}
|
||||||
@@ -59,11 +78,13 @@ function Mobile() {
|
|||||||
placeholder="Update your bio..."
|
placeholder="Update your bio..."
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full px-4 py-2 bg-success-600 text-white rounded"
|
className="w-full px-4 py-2 bg-success-600 text-white rounded"
|
||||||
>
|
>
|
||||||
Save Bio
|
Save Profile
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,120 +3,171 @@
|
|||||||
import { useDevice } from "@/lib/context/DeviceContext";
|
import { useDevice } from "@/lib/context/DeviceContext";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
// Default Profile Pictures are /avatar/p1.png to /avatar/p5.png
|
||||||
// please change names of pics below
|
|
||||||
|
|
||||||
const defaultPics = [
|
const defaultPics = [
|
||||||
"/pfps/default1.png",
|
{ src: "/avatar/p1.png", id: 1 },
|
||||||
"/pfps/default2.png",
|
{ src: "/avatar/p2.png", id: 2 },
|
||||||
"/pfps/default3.png",
|
{ src: "/avatar/p3.png", id: 3 },
|
||||||
"/pfps/default4.png",
|
{ src: "/avatar/p4.png", id: 4 },
|
||||||
|
{ src: "/avatar/p5.png", id: 5 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Purchaseable Profile Pictures are /avatar/p6.png to /avatar/p16.png
|
||||||
const purchasablePics = [
|
const purchasablePics = [
|
||||||
{ src: "/pfps/pic1.png", price: 250 },
|
{ src: "/avatar/p6.png", price: 250 },
|
||||||
{ src: "/pfps/pic2.png", price: 250 },
|
{ src: "/avatar/p7.png", price: 250 },
|
||||||
{ src: "/pfps/pic3.png", price: 2000 },
|
{ src: "/avatar/p8.png", price: 2000 },
|
||||||
{ src: "/pfps/pic4.png", price: 2000 },
|
{ src: "/avatar/p9.png", price: 2000 },
|
||||||
{ src: "/pfps/pic5.png", price: 2000 },
|
{ src: "/avatar/p10.png", price: 2000 },
|
||||||
{ src: "/pfps/pic6.png", price: 2000 },
|
{ src: "/avatar/p11.png", price: 2000 },
|
||||||
{ src: "/pfps/pic11.png", price: 2000 },
|
{ src: "/avatar/p12.png", price: 2000 },
|
||||||
{ src: "/pfps/pic12.png", price: 2000 },
|
{ src: "/avatar/p13.png", price: 10000 },
|
||||||
{ src: "/pfps/pic7.png", price: 10000 },
|
{ src: "/avatar/p14.png", price: 10000 },
|
||||||
{ src: "/pfps/pic8.png", price: 10000 },
|
{ src: "/avatar/p15.png", price: 10000 },
|
||||||
{ src: "/pfps/pic9.png", price: 10000 },
|
{ src: "/avatar/p16.png", price: 10000 },
|
||||||
{ src: "/pfps/pic10.png", price: 10000 },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function ShopPage() {
|
export default function ShopPage() {
|
||||||
const { isAuthenticated, session } = useDevice();
|
const { isAuthenticated, session } = useDevice();
|
||||||
const [totalPoints, setTotalPoints] = useState(0); // placeholder PLEASE PUT SMTH HERE
|
const [points, setPoints] = useState(session?.points || 0);
|
||||||
const [ownedPics, setOwnedPics] = useState<string[]>([...defaultPics]);
|
const [avatar, setAvatar] = useState(session?.avatar || 1);
|
||||||
const [currentPic, setCurrentPic] = useState(defaultPics[0]);
|
const [ownedPics, setOwnedPics] = useState<string[]>(session?.ownedPics || []);
|
||||||
|
|
||||||
const handlePurchase = (src: string, price: number) => {
|
const handleAvatarSelect = async (id: number) => {
|
||||||
if (ownedPics.includes(src)) return;
|
const formData = new FormData();
|
||||||
if (totalPoints >= price) {
|
formData.append("avatar", id.toString());
|
||||||
setTotalPoints(totalPoints - price);
|
|
||||||
setOwnedPics([...ownedPics, src]);
|
|
||||||
} else {
|
|
||||||
alert("Not enough points!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
try {
|
||||||
<div className="px-6 py-10 max-w-full lg:max-w-4xl mx-auto font-sans text-neutral-100">
|
const response = await fetch("/api/me", {
|
||||||
{/* User Header */}
|
method: "POST",
|
||||||
<div className="flex items-center gap-4 mb-6">
|
body: formData,
|
||||||
<div className="w-16 h-16 rounded-full bg-neutral-800 border border-gray-300">
|
});
|
||||||
<img src={currentPic} alt="Current PFP" className="rounded-full w-full h-full object-cover" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="bg-[color:var(--color-surface-600)]/60 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
|
||||||
<h1 className="text-2xl font-bold text-[color:var(--color-warning-300)]">
|
|
||||||
{isAuthenticated ? session.username : "Username"}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-[color:var(--color-success-600)] rounded-xl px-6 py-4 mb-8 shadow-sm">
|
const data = await response.json();
|
||||||
<h2 className="text-xl font-bold text-[color:var(--color-warning-300)] mb-1">Total Points</h2>
|
if (response.ok) {
|
||||||
<p className="text-lg">{totalPoints}</p>
|
alert("Avatar updated successfully!");
|
||||||
</div>
|
setAvatar(id);
|
||||||
|
} else {
|
||||||
|
alert(data.message || "Failed to update avatar.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating avatar:", error);
|
||||||
|
alert("An error occurred while updating the avatar.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePurchase = async (src: string, price: number) => {
|
||||||
|
if (points < price) {
|
||||||
|
alert("You don't have enough points to purchase this profile picture.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
<div className="mb-8">
|
// Update the user's avatar and points on the server
|
||||||
<div className="bg-[color:var(--color-success-600)]/70 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
const formData = new FormData();
|
||||||
<h2 className="text-xl font-semibold text-[color:var(--color-warning-300)] mb-0">Default Profile Pics</h2>
|
formData.append("avatar", src);
|
||||||
</div>
|
formData.append("points", (points - price).toString());
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
||||||
{defaultPics.map((src) => (
|
|
||||||
<img
|
|
||||||
key={src}
|
|
||||||
src={src}
|
|
||||||
alt="Default PFP"
|
|
||||||
className="rounded-xl w-full h-24 object-cover border border-gray-600"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Shop Grid */}
|
try {
|
||||||
<div>
|
const response = await fetch("/api/me", {
|
||||||
<div className="bg-[color:var(--color-tertiary-600)]/90 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
method: "POST",
|
||||||
<h2 className="text-xl font-semibold text-[color:var(--color-warning-200)] mb-0">Purchase New Profile Pics!</h2>
|
body: formData,
|
||||||
</div>
|
});
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-6">
|
|
||||||
{purchasablePics.map((pic) => (
|
const data = await response.json();
|
||||||
<div
|
if (response.ok) {
|
||||||
key={pic.src}
|
alert("Profile picture purchased successfully!");
|
||||||
className="bg-[color:var(--color-surface-600)] rounded-lg p-3 flex flex-col items-center text-center shadow-sm"
|
setPoints(points - price);
|
||||||
>
|
setAvatar(src);
|
||||||
<img
|
setOwnedPics([...ownedPics, src]);
|
||||||
src={pic.src}
|
} else {
|
||||||
alt="PFP"
|
alert(data.message || "Failed to purchase profile picture.");
|
||||||
className="w-24 h-24 object-cover rounded border border-gray-500 mb-2"
|
}
|
||||||
/>
|
} catch (error) {
|
||||||
<p className="text-sm mb-1">{pic.price} pts</p>
|
console.error("Error purchasing profile picture:", error);
|
||||||
<button
|
alert("An error occurred while purchasing the profile picture.");
|
||||||
className="px-3 py-1 bg-success-600 text-sm text-white rounded hover:bg-success-700"
|
}
|
||||||
onClick={() => handlePurchase(pic.src, pic.price)}
|
};
|
||||||
disabled={ownedPics.includes(pic.src)}
|
|
||||||
>
|
return (
|
||||||
{ownedPics.includes(pic.src) ? "Owned" : "Buy"}
|
<div className="px-6 py-10 max-w-full lg:max-w-4xl mx-auto font-sans text-neutral-100">
|
||||||
</button>
|
{/* User Header */}
|
||||||
|
<div className="items-center gap-4 mb-6">
|
||||||
|
<div className="w-full text-center bg-[color:var(--color-surface-600)]/60 backdrop-blur-md rounded-xl px-6 py-5 my-6 gap-4 shadow-sm inline-flex items-center">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-neutral-800">
|
||||||
|
<img
|
||||||
|
src={"/avatar/p" + avatar + ".png"}
|
||||||
|
alt="Current PFP"
|
||||||
|
className="rounded-full w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-[color:var(--color-warning-300)]">
|
||||||
|
{isAuthenticated ? session.username : "Username"}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Total Points */}
|
||||||
|
<div className="bg-[color:var(--color-success-600)] rounded-xl px-6 py-4 mb-8 shadow-sm">
|
||||||
|
<h2 className="text-xl font-bold text-[color:var(--color-warning-300)] mb-1">
|
||||||
|
Total Points
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg">{points}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Default Profile Pictures */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="bg-[color:var(--color-success-600)]/70 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
||||||
|
<h2 className="text-xl font-semibold text-[color:var(--color-warning-300)] mb-0">
|
||||||
|
Default Profile Pics
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
|
{defaultPics.map((pfp) => (
|
||||||
|
<div
|
||||||
|
key={pfp.src}
|
||||||
|
className="bg-[color:var(--color-surface-600)] rounded-lg p-3 flex flex-col items-center text-center shadow-sm"
|
||||||
|
onClick={() => handleAvatarSelect(pfp.id)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={pfp.src}
|
||||||
|
alt="PFP"
|
||||||
|
className="w-32 h-32 object-cover rounded mb-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Purchaseable Profile Pictures */}
|
||||||
|
<div>
|
||||||
|
<div className="bg-[color:var(--color-tertiary-600)]/90 backdrop-blur-md rounded-xl px-6 py-5 my-6 shadow-sm">
|
||||||
|
<h2 className="text-xl font-semibold text-[color:var(--color-warning-200)] mb-0">
|
||||||
|
Purchase New Profile Pics!
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-6">
|
||||||
|
{purchasablePics.map((pic) => (
|
||||||
|
<div
|
||||||
|
key={pic.src}
|
||||||
|
className="bg-[color:var(--color-surface-600)] rounded-lg p-3 flex flex-col items-center text-center shadow-sm"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={pic.src}
|
||||||
|
alt="PFP"
|
||||||
|
className="w-24 h-24 object-cover rounded border border-gray-500 mb-2"
|
||||||
|
/>
|
||||||
|
<p className="text-sm mb-1">{pic.price} pts</p>
|
||||||
|
<button
|
||||||
|
className="px-3 py-1 bg-success-600 text-sm text-white rounded hover:bg-success-700"
|
||||||
|
onClick={() => handlePurchase(pic.src, pic.price)}
|
||||||
|
disabled={ownedPics.includes(pic.src)}
|
||||||
|
>
|
||||||
|
{ownedPics.includes(pic.src) ? "Owned" : "Buy"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
|
|
||||||
<div className="h-6" />
|
|
||||||
<div className="h-6" />
|
|
||||||
<div className="h-6" />
|
|
||||||
<div className="h-6" />
|
|
||||||
<div className="h-6" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,43 @@ export async function POST(req: Request) {
|
|||||||
if (!userData) return NextResponse.json({ message: "User not found" }, { status: 404 });
|
if (!userData) return NextResponse.json({ message: "User not found" }, { status: 404 });
|
||||||
|
|
||||||
const formData = await req.formData();
|
const formData = await req.formData();
|
||||||
|
if (!formData) return NextResponse.json({ message: "No form data found" }, { status: 400 });
|
||||||
|
|
||||||
let bio = formData.get("bio");
|
let bio = formData.get("bio");
|
||||||
if(bio) {
|
if(bio) {
|
||||||
userData = await db.users.update(userData.id, { bio: bio.toString() });
|
userData = await db.users.update(userData.id, { bio: bio });
|
||||||
if (!userData) return NextResponse.json({ message: "Failed to update bio" }, { status: 500 });
|
if (!userData) return NextResponse.json({ message: "Failed to update bio" }, { status: 500 });
|
||||||
return NextResponse.json({ message: "Bio updated successfully" }, { status: 200 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let avatar = formData.get("avatar");
|
||||||
|
if(avatar) {
|
||||||
|
userData = await db.users.update(userData.id, { avatar: parseInt(avatar.toString()) });
|
||||||
|
if (!userData) return NextResponse.json({ message: "Failed to update avatar" }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = formData.get("username");
|
||||||
|
if(username) {
|
||||||
|
userData = await db.users.update(userData.id, { username: username });
|
||||||
|
if (!userData) return NextResponse.json({ message: "Failed to update username" }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let points = formData.get("points");
|
||||||
|
if(points) {
|
||||||
|
userData = await db.users.update(userData.id, { points: parseInt(points.toString()) });
|
||||||
|
if (!userData) return NextResponse.json({ message: "Failed to update points" }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let inventory = formData.get("inventory");
|
||||||
|
if(inventory) {
|
||||||
|
let inventoryData = userData.inventory;
|
||||||
|
if (!inventoryData) inventoryData = [];
|
||||||
|
inventoryData.push(inventory.toString());
|
||||||
|
|
||||||
|
userData = await db.users.update(userData.id, { inventory: inventoryData });
|
||||||
|
if (!userData) return NextResponse.json({ message: "Failed to update inventory" }, { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
return NextResponse.json({ message: "No Update" }, { status: 400 });
|
return NextResponse.json({ message: "User updated successfully", user: userData }, { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating user bio:", error);
|
console.error("Error updating user bio:", error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
Home as IconHome,
|
Home as IconHome,
|
||||||
BookText as BookImage,
|
BookText as BookImage,
|
||||||
User as UserImage,
|
User as UserImage,
|
||||||
Plus as PlusImage
|
Plus as PlusImage,
|
||||||
|
ShoppingCart as ShoppingCartImage,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { useDevice } from "@/lib/context/DeviceContext";
|
import { useDevice } from "@/lib/context/DeviceContext";
|
||||||
@@ -28,6 +29,11 @@ const MobileNav = () => {
|
|||||||
<PlusImage />
|
<PlusImage />
|
||||||
</Navigation.Tile>
|
</Navigation.Tile>
|
||||||
) : null}
|
) : null}
|
||||||
|
{isAuthenticated ? (
|
||||||
|
<Navigation.Tile label="Shop" href="/shop" className="flex flex-col items-center">
|
||||||
|
<ShoppingCartImage />
|
||||||
|
</Navigation.Tile>
|
||||||
|
) : null}
|
||||||
{isAuthenticated ? (
|
{isAuthenticated ? (
|
||||||
<Navigation.Tile label="Profile" href="/profile" className="flex flex-col items-center">
|
<Navigation.Tile label="Profile" href="/profile" className="flex flex-col items-center">
|
||||||
<UserImage />
|
<UserImage />
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(Id: string, data: any) {
|
async update(Id: string, data: any) {
|
||||||
return await this.model.updateOne({ id: Id }, { $set: data }, this.upsert);
|
await this.model.updateOne({ id: Id }, { $set: data }, this.upsert);
|
||||||
|
return await this.model.findOne({ id: Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(Id: string) {
|
async delete(Id: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user