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() {
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("Please enter a bio.");
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 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>

View File

@@ -3,89 +3,147 @@
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 [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);
const handleAvatarSelect = async (id: number) => {
const formData = new FormData();
formData.append("avatar", id.toString());
try {
const response = await fetch("/api/me", {
method: "POST",
body: formData,
});
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;
}
// Update the user's avatar and points on the server
const formData = new FormData();
formData.append("avatar", src);
formData.append("points", (points - price).toString());
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("Not enough points!");
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="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 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>
<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>
{/* 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">{totalPoints}</p>
<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>
<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) => (
{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
key={src}
src={src}
alt="Default PFP"
className="rounded-xl w-full h-24 object-cover border border-gray-600"
src={pfp.src}
alt="PFP"
className="w-32 h-32 object-cover rounded mb-2"
/>
</div>
))}
</div>
</div>
{/* Shop Grid */}
{/* 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>
<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) => (
@@ -110,13 +168,6 @@ export default function ShopPage() {
))}
</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 });
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 });
}
return NextResponse.json({ message: "No Update" }, { status: 400 });
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: "User updated successfully", user: userData }, { status: 200 });
} catch (error) {
console.error("Error updating user bio:", error);
return NextResponse.json(

View File

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

View File

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