Shop Update

This commit is contained in:
2025-04-13 04:33:46 -04:00
parent 3028419660
commit 01d8986dd2
5 changed files with 226 additions and 120 deletions

View File

@@ -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>

View File

@@ -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>
);
} }

View File

@@ -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(

View File

@@ -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 />

View File

@@ -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) {