Shop Update
This commit is contained in:
@@ -6,28 +6,40 @@ import { useDevice } from "@/lib/context/DeviceContext";
|
||||
function Mobile() {
|
||||
const { isAuthenticated, session } = useDevice();
|
||||
const [bio, setBio] = useState(session?.bio || "");
|
||||
const [username, setUsername] = useState(session?.username || "");
|
||||
|
||||
useEffect(() => {
|
||||
if (session) {
|
||||
setBio(session.bio || "");
|
||||
setUsername(session.username || "");
|
||||
}
|
||||
}, [session]);
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (bio.length > 0) {
|
||||
if (bio.length > 0 || username.length > 0) {
|
||||
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", {
|
||||
method: "POST",
|
||||
body: formData, // Automatically sets Content-Type to multipart/form-data
|
||||
});
|
||||
|
||||
// Show a success message
|
||||
alert("Bio saved!");
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
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 {
|
||||
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="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">
|
||||
Hi, {session?.username || ""}!!
|
||||
Hi, {username || ""}!!
|
||||
</h1>
|
||||
|
||||
<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"
|
||||
/>
|
||||
)}
|
||||
{/* {uploadMessage && (
|
||||
<p className="text-sm text-gray-400 mt-2">{uploadMessage}</p>
|
||||
)} */}
|
||||
</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
|
||||
className="w-full p-2 rounded bg-neutral-800 text-white"
|
||||
onChange={(e) => setBio(e.target.value)}
|
||||
@@ -59,11 +78,13 @@ function Mobile() {
|
||||
placeholder="Update your bio..."
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full px-4 py-2 bg-success-600 text-white rounded"
|
||||
>
|
||||
Save Bio
|
||||
Save Profile
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -3,120 +3,171 @@
|
||||
import { useDevice } from "@/lib/context/DeviceContext";
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
// please change names of pics below
|
||||
|
||||
// Default Profile Pictures are /avatar/p1.png to /avatar/p5.png
|
||||
const defaultPics = [
|
||||
"/pfps/default1.png",
|
||||
"/pfps/default2.png",
|
||||
"/pfps/default3.png",
|
||||
"/pfps/default4.png",
|
||||
{ src: "/avatar/p1.png", id: 1 },
|
||||
{ src: "/avatar/p2.png", id: 2 },
|
||||
{ src: "/avatar/p3.png", id: 3 },
|
||||
{ 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 = [
|
||||
{ src: "/pfps/pic1.png", price: 250 },
|
||||
{ src: "/pfps/pic2.png", price: 250 },
|
||||
{ src: "/pfps/pic3.png", price: 2000 },
|
||||
{ src: "/pfps/pic4.png", price: 2000 },
|
||||
{ src: "/pfps/pic5.png", price: 2000 },
|
||||
{ src: "/pfps/pic6.png", price: 2000 },
|
||||
{ src: "/pfps/pic11.png", price: 2000 },
|
||||
{ src: "/pfps/pic12.png", price: 2000 },
|
||||
{ src: "/pfps/pic7.png", price: 10000 },
|
||||
{ src: "/pfps/pic8.png", price: 10000 },
|
||||
{ src: "/pfps/pic9.png", price: 10000 },
|
||||
{ src: "/pfps/pic10.png", price: 10000 },
|
||||
{ src: "/avatar/p6.png", price: 250 },
|
||||
{ src: "/avatar/p7.png", price: 250 },
|
||||
{ src: "/avatar/p8.png", price: 2000 },
|
||||
{ src: "/avatar/p9.png", price: 2000 },
|
||||
{ src: "/avatar/p10.png", price: 2000 },
|
||||
{ src: "/avatar/p11.png", price: 2000 },
|
||||
{ src: "/avatar/p12.png", price: 2000 },
|
||||
{ src: "/avatar/p13.png", price: 10000 },
|
||||
{ src: "/avatar/p14.png", price: 10000 },
|
||||
{ src: "/avatar/p15.png", price: 10000 },
|
||||
{ src: "/avatar/p16.png", price: 10000 },
|
||||
];
|
||||
|
||||
export default function ShopPage() {
|
||||
const { isAuthenticated, session } = useDevice();
|
||||
const [totalPoints, setTotalPoints] = useState(0); // placeholder PLEASE PUT SMTH HERE
|
||||
const [ownedPics, setOwnedPics] = useState<string[]>([...defaultPics]);
|
||||
const [currentPic, setCurrentPic] = useState(defaultPics[0]);
|
||||
const { isAuthenticated, session } = useDevice();
|
||||
const [points, setPoints] = useState(session?.points || 0);
|
||||
const [avatar, setAvatar] = useState(session?.avatar || 1);
|
||||
const [ownedPics, setOwnedPics] = useState<string[]>(session?.ownedPics || []);
|
||||
|
||||
const handlePurchase = (src: string, price: number) => {
|
||||
if (ownedPics.includes(src)) return;
|
||||
if (totalPoints >= price) {
|
||||
setTotalPoints(totalPoints - price);
|
||||
setOwnedPics([...ownedPics, src]);
|
||||
} else {
|
||||
alert("Not enough points!");
|
||||
}
|
||||
};
|
||||
const handleAvatarSelect = async (id: number) => {
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", id.toString());
|
||||
|
||||
return (
|
||||
<div className="px-6 py-10 max-w-full lg:max-w-4xl mx-auto font-sans text-neutral-100">
|
||||
{/* User Header */}
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<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>
|
||||
try {
|
||||
const response = await fetch("/api/me", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
<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">{totalPoints}</p>
|
||||
</div>
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
alert("Avatar updated successfully!");
|
||||
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">
|
||||
<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((src) => (
|
||||
<img
|
||||
key={src}
|
||||
src={src}
|
||||
alt="Default PFP"
|
||||
className="rounded-xl w-full h-24 object-cover border border-gray-600"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
// Update the user's avatar and points on the server
|
||||
const formData = new FormData();
|
||||
formData.append("avatar", src);
|
||||
formData.append("points", (points - price).toString());
|
||||
|
||||
{/* Shop Grid */}
|
||||
<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>
|
||||
try {
|
||||
const response = await fetch("/api/me", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
alert("Profile picture purchased successfully!");
|
||||
setPoints(points - price);
|
||||
setAvatar(src);
|
||||
setOwnedPics([...ownedPics, src]);
|
||||
} else {
|
||||
alert(data.message || "Failed to purchase profile picture.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error purchasing profile picture:", error);
|
||||
alert("An error occurred while purchasing the profile picture.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-6 py-10 max-w-full lg:max-w-4xl mx-auto font-sans text-neutral-100">
|
||||
{/* 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 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 });
|
||||
|
||||
const formData = await req.formData();
|
||||
|
||||
if (!formData) return NextResponse.json({ message: "No form data found" }, { status: 400 });
|
||||
|
||||
let bio = formData.get("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 });
|
||||
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) {
|
||||
console.error("Error updating user bio:", error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
Home as IconHome,
|
||||
BookText as BookImage,
|
||||
User as UserImage,
|
||||
Plus as PlusImage
|
||||
Plus as PlusImage,
|
||||
ShoppingCart as ShoppingCartImage,
|
||||
} from "lucide-react";
|
||||
|
||||
import { useDevice } from "@/lib/context/DeviceContext";
|
||||
@@ -28,6 +29,11 @@ const MobileNav = () => {
|
||||
<PlusImage />
|
||||
</Navigation.Tile>
|
||||
) : null}
|
||||
{isAuthenticated ? (
|
||||
<Navigation.Tile label="Shop" href="/shop" className="flex flex-col items-center">
|
||||
<ShoppingCartImage />
|
||||
</Navigation.Tile>
|
||||
) : null}
|
||||
{isAuthenticated ? (
|
||||
<Navigation.Tile label="Profile" href="/profile" className="flex flex-col items-center">
|
||||
<UserImage />
|
||||
|
||||
@@ -72,7 +72,8 @@ export class User {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user