Functional cart and balance display
This commit is contained in:
parent
bcc5164abb
commit
ed5afb1dec
@ -33,6 +33,8 @@ export default tseslint.config(
|
|||||||
"error",
|
"error",
|
||||||
{ checksVoidReturn: { attributes: false } },
|
{ checksVoidReturn: { attributes: false } },
|
||||||
],
|
],
|
||||||
|
// ignore
|
||||||
|
"@next/next/no-img-element": "off",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -181,7 +181,6 @@ exports.Prisma.SellableScalarFieldEnum = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.Prisma.CartScalarFieldEnum = {
|
exports.Prisma.CartScalarFieldEnum = {
|
||||||
id: 'id',
|
|
||||||
userId: 'userId'
|
userId: 'userId'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -254,7 +253,6 @@ exports.Prisma.SellableOrderByRelevanceFieldEnum = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.Prisma.CartOrderByRelevanceFieldEnum = {
|
exports.Prisma.CartOrderByRelevanceFieldEnum = {
|
||||||
id: 'id',
|
|
||||||
userId: 'userId'
|
userId: 'userId'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
52
generated/prisma/index.d.ts
vendored
52
generated/prisma/index.d.ts
vendored
@ -8745,34 +8745,28 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartMinAggregateOutputType = {
|
export type CartMinAggregateOutputType = {
|
||||||
id: string | null
|
|
||||||
userId: string | null
|
userId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartMaxAggregateOutputType = {
|
export type CartMaxAggregateOutputType = {
|
||||||
id: string | null
|
|
||||||
userId: string | null
|
userId: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartCountAggregateOutputType = {
|
export type CartCountAggregateOutputType = {
|
||||||
id: number
|
|
||||||
userId: number
|
userId: number
|
||||||
_all: number
|
_all: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type CartMinAggregateInputType = {
|
export type CartMinAggregateInputType = {
|
||||||
id?: true
|
|
||||||
userId?: true
|
userId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartMaxAggregateInputType = {
|
export type CartMaxAggregateInputType = {
|
||||||
id?: true
|
|
||||||
userId?: true
|
userId?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartCountAggregateInputType = {
|
export type CartCountAggregateInputType = {
|
||||||
id?: true
|
|
||||||
userId?: true
|
userId?: true
|
||||||
_all?: true
|
_all?: true
|
||||||
}
|
}
|
||||||
@ -8850,7 +8844,6 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartGroupByOutputType = {
|
export type CartGroupByOutputType = {
|
||||||
id: string
|
|
||||||
userId: string
|
userId: string
|
||||||
_count: CartCountAggregateOutputType | null
|
_count: CartCountAggregateOutputType | null
|
||||||
_min: CartMinAggregateOutputType | null
|
_min: CartMinAggregateOutputType | null
|
||||||
@ -8872,7 +8865,6 @@ export namespace Prisma {
|
|||||||
|
|
||||||
|
|
||||||
export type CartSelect<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
|
export type CartSelect<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
|
||||||
id?: boolean
|
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
user?: boolean | UserDefaultArgs<ExtArgs>
|
user?: boolean | UserDefaultArgs<ExtArgs>
|
||||||
cartItems?: boolean | Cart$cartItemsArgs<ExtArgs>
|
cartItems?: boolean | Cart$cartItemsArgs<ExtArgs>
|
||||||
@ -8882,11 +8874,10 @@ export namespace Prisma {
|
|||||||
|
|
||||||
|
|
||||||
export type CartSelectScalar = {
|
export type CartSelectScalar = {
|
||||||
id?: boolean
|
|
||||||
userId?: boolean
|
userId?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"id" | "userId", ExtArgs["result"]["cart"]>
|
export type CartOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"userId", ExtArgs["result"]["cart"]>
|
||||||
export type CartInclude<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
|
export type CartInclude<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
|
||||||
user?: boolean | UserDefaultArgs<ExtArgs>
|
user?: boolean | UserDefaultArgs<ExtArgs>
|
||||||
cartItems?: boolean | Cart$cartItemsArgs<ExtArgs>
|
cartItems?: boolean | Cart$cartItemsArgs<ExtArgs>
|
||||||
@ -8900,7 +8891,6 @@ export namespace Prisma {
|
|||||||
cartItems: Prisma.$CartItemPayload<ExtArgs>[]
|
cartItems: Prisma.$CartItemPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: $Extensions.GetPayloadResult<{
|
scalars: $Extensions.GetPayloadResult<{
|
||||||
id: string
|
|
||||||
userId: string
|
userId: string
|
||||||
}, ExtArgs["result"]["cart"]>
|
}, ExtArgs["result"]["cart"]>
|
||||||
composites: {}
|
composites: {}
|
||||||
@ -8985,8 +8975,8 @@ export namespace Prisma {
|
|||||||
* // Get first 10 Carts
|
* // Get first 10 Carts
|
||||||
* const carts = await prisma.cart.findMany({ take: 10 })
|
* const carts = await prisma.cart.findMany({ take: 10 })
|
||||||
*
|
*
|
||||||
* // Only select the `id`
|
* // Only select the `userId`
|
||||||
* const cartWithIdOnly = await prisma.cart.findMany({ select: { id: true } })
|
* const cartWithUserIdOnly = await prisma.cart.findMany({ select: { userId: true } })
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
findMany<T extends CartFindManyArgs>(args?: SelectSubset<T, CartFindManyArgs<ExtArgs>>): Prisma.PrismaPromise<$Result.GetResult<Prisma.$CartPayload<ExtArgs>, T, "findMany", GlobalOmitOptions>>
|
findMany<T extends CartFindManyArgs>(args?: SelectSubset<T, CartFindManyArgs<ExtArgs>>): Prisma.PrismaPromise<$Result.GetResult<Prisma.$CartPayload<ExtArgs>, T, "findMany", GlobalOmitOptions>>
|
||||||
@ -9273,7 +9263,6 @@ export namespace Prisma {
|
|||||||
* Fields of the Cart model
|
* Fields of the Cart model
|
||||||
*/
|
*/
|
||||||
interface CartFieldRefs {
|
interface CartFieldRefs {
|
||||||
readonly id: FieldRef<"Cart", 'String'>
|
|
||||||
readonly userId: FieldRef<"Cart", 'String'>
|
readonly userId: FieldRef<"Cart", 'String'>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11607,7 +11596,6 @@ export namespace Prisma {
|
|||||||
|
|
||||||
|
|
||||||
export const CartScalarFieldEnum: {
|
export const CartScalarFieldEnum: {
|
||||||
id: 'id',
|
|
||||||
userId: 'userId'
|
userId: 'userId'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11716,7 +11704,6 @@ export namespace Prisma {
|
|||||||
|
|
||||||
|
|
||||||
export const CartOrderByRelevanceFieldEnum: {
|
export const CartOrderByRelevanceFieldEnum: {
|
||||||
id: 'id',
|
|
||||||
userId: 'userId'
|
userId: 'userId'
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -12230,14 +12217,12 @@ export namespace Prisma {
|
|||||||
AND?: CartWhereInput | CartWhereInput[]
|
AND?: CartWhereInput | CartWhereInput[]
|
||||||
OR?: CartWhereInput[]
|
OR?: CartWhereInput[]
|
||||||
NOT?: CartWhereInput | CartWhereInput[]
|
NOT?: CartWhereInput | CartWhereInput[]
|
||||||
id?: StringFilter<"Cart"> | string
|
|
||||||
userId?: StringFilter<"Cart"> | string
|
userId?: StringFilter<"Cart"> | string
|
||||||
user?: XOR<UserScalarRelationFilter, UserWhereInput>
|
user?: XOR<UserScalarRelationFilter, UserWhereInput>
|
||||||
cartItems?: CartItemListRelationFilter
|
cartItems?: CartItemListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartOrderByWithRelationInput = {
|
export type CartOrderByWithRelationInput = {
|
||||||
id?: SortOrder
|
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
user?: UserOrderByWithRelationInput
|
user?: UserOrderByWithRelationInput
|
||||||
cartItems?: CartItemOrderByRelationAggregateInput
|
cartItems?: CartItemOrderByRelationAggregateInput
|
||||||
@ -12245,17 +12230,15 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartWhereUniqueInput = Prisma.AtLeast<{
|
export type CartWhereUniqueInput = Prisma.AtLeast<{
|
||||||
id?: string
|
userId?: string
|
||||||
AND?: CartWhereInput | CartWhereInput[]
|
AND?: CartWhereInput | CartWhereInput[]
|
||||||
OR?: CartWhereInput[]
|
OR?: CartWhereInput[]
|
||||||
NOT?: CartWhereInput | CartWhereInput[]
|
NOT?: CartWhereInput | CartWhereInput[]
|
||||||
userId?: StringFilter<"Cart"> | string
|
|
||||||
user?: XOR<UserScalarRelationFilter, UserWhereInput>
|
user?: XOR<UserScalarRelationFilter, UserWhereInput>
|
||||||
cartItems?: CartItemListRelationFilter
|
cartItems?: CartItemListRelationFilter
|
||||||
}, "id">
|
}, "userId">
|
||||||
|
|
||||||
export type CartOrderByWithAggregationInput = {
|
export type CartOrderByWithAggregationInput = {
|
||||||
id?: SortOrder
|
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
_count?: CartCountOrderByAggregateInput
|
_count?: CartCountOrderByAggregateInput
|
||||||
_max?: CartMaxOrderByAggregateInput
|
_max?: CartMaxOrderByAggregateInput
|
||||||
@ -12266,7 +12249,6 @@ export namespace Prisma {
|
|||||||
AND?: CartScalarWhereWithAggregatesInput | CartScalarWhereWithAggregatesInput[]
|
AND?: CartScalarWhereWithAggregatesInput | CartScalarWhereWithAggregatesInput[]
|
||||||
OR?: CartScalarWhereWithAggregatesInput[]
|
OR?: CartScalarWhereWithAggregatesInput[]
|
||||||
NOT?: CartScalarWhereWithAggregatesInput | CartScalarWhereWithAggregatesInput[]
|
NOT?: CartScalarWhereWithAggregatesInput | CartScalarWhereWithAggregatesInput[]
|
||||||
id?: StringWithAggregatesFilter<"Cart"> | string
|
|
||||||
userId?: StringWithAggregatesFilter<"Cart"> | string
|
userId?: StringWithAggregatesFilter<"Cart"> | string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12811,40 +12793,34 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartCreateInput = {
|
export type CartCreateInput = {
|
||||||
id: string
|
|
||||||
user: UserCreateNestedOneWithoutCartsInput
|
user: UserCreateNestedOneWithoutCartsInput
|
||||||
cartItems?: CartItemCreateNestedManyWithoutCartInput
|
cartItems?: CartItemCreateNestedManyWithoutCartInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedCreateInput = {
|
export type CartUncheckedCreateInput = {
|
||||||
id: string
|
|
||||||
userId: string
|
userId: string
|
||||||
cartItems?: CartItemUncheckedCreateNestedManyWithoutCartInput
|
cartItems?: CartItemUncheckedCreateNestedManyWithoutCartInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUpdateInput = {
|
export type CartUpdateInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
user?: UserUpdateOneRequiredWithoutCartsNestedInput
|
user?: UserUpdateOneRequiredWithoutCartsNestedInput
|
||||||
cartItems?: CartItemUpdateManyWithoutCartNestedInput
|
cartItems?: CartItemUpdateManyWithoutCartNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedUpdateInput = {
|
export type CartUncheckedUpdateInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
userId?: StringFieldUpdateOperationsInput | string
|
userId?: StringFieldUpdateOperationsInput | string
|
||||||
cartItems?: CartItemUncheckedUpdateManyWithoutCartNestedInput
|
cartItems?: CartItemUncheckedUpdateManyWithoutCartNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartCreateManyInput = {
|
export type CartCreateManyInput = {
|
||||||
id: string
|
|
||||||
userId: string
|
userId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUpdateManyMutationInput = {
|
export type CartUpdateManyMutationInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedUpdateManyInput = {
|
export type CartUncheckedUpdateManyInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
userId?: StringFieldUpdateOperationsInput | string
|
userId?: StringFieldUpdateOperationsInput | string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13523,17 +13499,14 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartCountOrderByAggregateInput = {
|
export type CartCountOrderByAggregateInput = {
|
||||||
id?: SortOrder
|
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartMaxOrderByAggregateInput = {
|
export type CartMaxOrderByAggregateInput = {
|
||||||
id?: SortOrder
|
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartMinOrderByAggregateInput = {
|
export type CartMinOrderByAggregateInput = {
|
||||||
id?: SortOrder
|
|
||||||
userId?: SortOrder
|
userId?: SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14651,12 +14624,10 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartCreateWithoutUserInput = {
|
export type CartCreateWithoutUserInput = {
|
||||||
id: string
|
|
||||||
cartItems?: CartItemCreateNestedManyWithoutCartInput
|
cartItems?: CartItemCreateNestedManyWithoutCartInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedCreateWithoutUserInput = {
|
export type CartUncheckedCreateWithoutUserInput = {
|
||||||
id: string
|
|
||||||
cartItems?: CartItemUncheckedCreateNestedManyWithoutCartInput
|
cartItems?: CartItemUncheckedCreateNestedManyWithoutCartInput
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14796,7 +14767,6 @@ export namespace Prisma {
|
|||||||
AND?: CartScalarWhereInput | CartScalarWhereInput[]
|
AND?: CartScalarWhereInput | CartScalarWhereInput[]
|
||||||
OR?: CartScalarWhereInput[]
|
OR?: CartScalarWhereInput[]
|
||||||
NOT?: CartScalarWhereInput | CartScalarWhereInput[]
|
NOT?: CartScalarWhereInput | CartScalarWhereInput[]
|
||||||
id?: StringFilter<"Cart"> | string
|
|
||||||
userId?: StringFilter<"Cart"> | string
|
userId?: StringFilter<"Cart"> | string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15318,12 +15288,10 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartCreateWithoutCartItemsInput = {
|
export type CartCreateWithoutCartItemsInput = {
|
||||||
id: string
|
|
||||||
user: UserCreateNestedOneWithoutCartsInput
|
user: UserCreateNestedOneWithoutCartsInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedCreateWithoutCartItemsInput = {
|
export type CartUncheckedCreateWithoutCartItemsInput = {
|
||||||
id: string
|
|
||||||
userId: string
|
userId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15367,12 +15335,10 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartUpdateWithoutCartItemsInput = {
|
export type CartUpdateWithoutCartItemsInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
user?: UserUpdateOneRequiredWithoutCartsNestedInput
|
user?: UserUpdateOneRequiredWithoutCartsNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedUpdateWithoutCartItemsInput = {
|
export type CartUncheckedUpdateWithoutCartItemsInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
userId?: StringFieldUpdateOperationsInput | string
|
userId?: StringFieldUpdateOperationsInput | string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15500,7 +15466,7 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartCreateManyUserInput = {
|
export type CartCreateManyUserInput = {
|
||||||
id: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdressCreateManyUserInput = {
|
export type AdressCreateManyUserInput = {
|
||||||
@ -15591,17 +15557,15 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartUpdateWithoutUserInput = {
|
export type CartUpdateWithoutUserInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
cartItems?: CartItemUpdateManyWithoutCartNestedInput
|
cartItems?: CartItemUpdateManyWithoutCartNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedUpdateWithoutUserInput = {
|
export type CartUncheckedUpdateWithoutUserInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
cartItems?: CartItemUncheckedUpdateManyWithoutCartNestedInput
|
cartItems?: CartItemUncheckedUpdateManyWithoutCartNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CartUncheckedUpdateManyWithoutUserInput = {
|
export type CartUncheckedUpdateManyWithoutUserInput = {
|
||||||
id?: StringFieldUpdateOperationsInput | string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdressUpdateWithoutUserInput = {
|
export type AdressUpdateWithoutUserInput = {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-1d666464292a599bad3179d365e160013075f010f27e15f565fd192ad5ef967d",
|
"name": "prisma-client-a0e1bef334afa5b609bdf0c15431f28835b869da62270d0786ce071578cdedb4",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "default.js",
|
"browser": "default.js",
|
||||||
|
|||||||
@ -107,8 +107,7 @@ model Sellable {
|
|||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
||||||
model Cart {
|
model Cart {
|
||||||
id String @id
|
userId String @id
|
||||||
userId String
|
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
cartItems CartItem[]
|
cartItems CartItem[]
|
||||||
@ -119,7 +118,7 @@ model CartItem {
|
|||||||
quantity Int
|
quantity Int
|
||||||
cartId String
|
cartId String
|
||||||
|
|
||||||
cart Cart @relation(fields: [cartId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
cart Cart @relation(fields: [cartId], references: [userId], onDelete: Cascade, onUpdate: Cascade)
|
||||||
sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -107,8 +107,7 @@ model Sellable {
|
|||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
||||||
model Cart {
|
model Cart {
|
||||||
id String @id
|
userId String @id
|
||||||
userId String
|
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
cartItems CartItem[]
|
cartItems CartItem[]
|
||||||
@ -119,7 +118,7 @@ model CartItem {
|
|||||||
quantity Int
|
quantity Int
|
||||||
cartId String
|
cartId String
|
||||||
|
|
||||||
cart Cart @relation(fields: [cartId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
cart Cart @relation(fields: [cartId], references: [userId], onDelete: Cascade, onUpdate: Cascade)
|
||||||
sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
src/app/api/buy/route.ts
Normal file
0
src/app/api/buy/route.ts
Normal file
83
src/app/api/cart/route.ts
Normal file
83
src/app/api/cart/route.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { auth } from "~/server/auth";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
|
||||||
|
export async function PATCH(request: Request) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
type CartItem = {
|
||||||
|
id: string;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
|
const body = (await request.json()) as CartItem;
|
||||||
|
const { id: itemId, quantity } = body;
|
||||||
|
|
||||||
|
if (!itemId || !quantity || quantity == 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Invalid request data" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find or create the user's cart
|
||||||
|
let cart = await db.cart.findFirst({
|
||||||
|
where: { userId },
|
||||||
|
include: { cartItems: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use nullish coalescing assignment
|
||||||
|
cart ??= await db.cart.create({
|
||||||
|
data: { userId },
|
||||||
|
include: { cartItems: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if the item already exists in the cart
|
||||||
|
const existingCartItem = await db.cartItem.findUnique({
|
||||||
|
where: { itemId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingCartItem) {
|
||||||
|
if (quantity <= 0 && quantity * -1 >= existingCartItem.quantity) {
|
||||||
|
await db.cartItem.delete({
|
||||||
|
where: { itemId },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Update quantity
|
||||||
|
await db.cartItem.update({
|
||||||
|
where: { itemId },
|
||||||
|
data: { quantity: existingCartItem.quantity + quantity },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add new item
|
||||||
|
await db.cartItem.create({
|
||||||
|
data: {
|
||||||
|
itemId,
|
||||||
|
quantity,
|
||||||
|
cartId: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the updated cart
|
||||||
|
const updatedCart = await db.cart.findUnique({
|
||||||
|
where: { userId: userId },
|
||||||
|
include: { cartItems: { include: { sellable: true } } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(updatedCart);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Failed to add item to cart" },
|
||||||
|
{ status: 500 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,9 @@ export async function GET() {
|
|||||||
item: { select: { stock: true } },
|
item: { select: { stock: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
// and cache no store header
|
||||||
|
|
||||||
return NextResponse.json(sellables);
|
return NextResponse.json(sellables, {
|
||||||
|
headers: { "Cache-Control": "no-store" },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,12 @@ export async function GET() {
|
|||||||
shops: {
|
shops: {
|
||||||
select: { id: true, label: true, sellables: true },
|
select: { id: true, label: true, sellables: true },
|
||||||
},
|
},
|
||||||
|
carts: {
|
||||||
|
select: {
|
||||||
|
cartItems: { select: { itemId: true, quantity: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
balance: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,66 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Items from "~/components/sellable_items";
|
import Items from "~/components/sellable_items";
|
||||||
import Search from "~/components/search";
|
import Search from "~/components/search";
|
||||||
|
import CartButton from "~/components/cart";
|
||||||
import type { Session } from "next-auth";
|
import type { Session } from "next-auth";
|
||||||
|
|
||||||
|
type UserResponse = {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
carts: {
|
||||||
|
cartItems: {
|
||||||
|
itemId: string;
|
||||||
|
quantity: number;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
balance: number;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
session: Session | null;
|
session: Session | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HomeClient({ session }: Props) {
|
export default function HomeClient({ session }: Props) {
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
const [userData, setUserData] = useState<UserResponse | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
// Fetch /api/user once and store globally here
|
||||||
|
const loadUser = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await fetch("/api/user");
|
||||||
|
if (!res.ok) throw new Error("Failed to fetch user");
|
||||||
|
const data = (await res.json()) as UserResponse;
|
||||||
|
setUserData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadUser();
|
||||||
|
}, [loadUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen flex-col items-center justify-center bg-linear-to-b from-[#2e026d] to-[#7f3b3b] text-white">
|
<main className="flex min-h-screen flex-col items-center justify-center bg-linear-to-b from-[#2e026d] to-[#7f3b3b] text-white">
|
||||||
<div className="absolute top-4 right-4 flex items-center gap-4">
|
<div className="absolute top-4 right-4 flex items-center gap-4">
|
||||||
|
{userData && (
|
||||||
|
<div className="rounded-full bg-white/10 px-4 py-2 font-semibold">
|
||||||
|
${userData.balance ?? 0}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<CartButton
|
||||||
|
userData={userData}
|
||||||
|
reloadUser={loadUser}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
|
||||||
<Search query={query} setQuery={setQuery} />
|
<Search query={query} setQuery={setQuery} />
|
||||||
|
|
||||||
{session?.user ? (
|
{session?.user ? (
|
||||||
@ -40,8 +85,7 @@ export default function HomeClient({ session }: Props) {
|
|||||||
Suchodupin <span className="text-[hsl(280,100%,70%)]">MC</span> Shop
|
Suchodupin <span className="text-[hsl(280,100%,70%)]">MC</span> Shop
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
{/* ✅ query flows down */}
|
<Items query={query} reloadUser={loadUser} />
|
||||||
<Items query={query} />
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
292
src/components/cart.tsx
Normal file
292
src/components/cart.tsx
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
|
||||||
|
type UserResponse = {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
carts: {
|
||||||
|
cartItems: {
|
||||||
|
itemId: string;
|
||||||
|
quantity: number;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type CartViewItem = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
actualQuantity: number;
|
||||||
|
itemAmount: number;
|
||||||
|
stock: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ApiItem = {
|
||||||
|
id: string;
|
||||||
|
item_name: string;
|
||||||
|
amount: number;
|
||||||
|
price: number;
|
||||||
|
enabled: boolean;
|
||||||
|
shop: {
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
item: {
|
||||||
|
stock: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatName = (name: string) => {
|
||||||
|
const parts = name.split(":");
|
||||||
|
if (!parts[1]) return "";
|
||||||
|
return parts[1]
|
||||||
|
.split("_")
|
||||||
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||||
|
.join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const getImage = (name: string) => {
|
||||||
|
const [mod, item] = name.split(":");
|
||||||
|
return `/textures/${mod}/${item}.png`;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CartButtonProps = {
|
||||||
|
userData: UserResponse | null;
|
||||||
|
reloadUser: () => Promise<void>;
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add at top of CartButton component
|
||||||
|
type DraggingState = {
|
||||||
|
itemId: string;
|
||||||
|
quantity: number;
|
||||||
|
max: number;
|
||||||
|
rect: DOMRect;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
export default function CartButton({
|
||||||
|
userData,
|
||||||
|
reloadUser,
|
||||||
|
loading,
|
||||||
|
}: CartButtonProps) {
|
||||||
|
const [cartItems, setCartItems] = useState<CartViewItem[]>([]);
|
||||||
|
const [totalQuantity, setTotalQuantity] = useState(0);
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const [dragging, setDragging] = useState<DraggingState>(null);
|
||||||
|
const [sliderActive, setSliderActive] = useState<string | null>(null);
|
||||||
|
const [holdTimeout, setHoldTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userData) return;
|
||||||
|
|
||||||
|
const fetchItems = async () => {
|
||||||
|
const res = await fetch("/api/items");
|
||||||
|
const items = (await res.json()) as ApiItem[];
|
||||||
|
const itemMap = new Map(items.map((i) => [i.id, i]));
|
||||||
|
|
||||||
|
let total = 0;
|
||||||
|
const collected: CartViewItem[] = [];
|
||||||
|
|
||||||
|
for (const cart of userData.carts ?? []) {
|
||||||
|
for (const cartItem of cart.cartItems) {
|
||||||
|
const item = itemMap.get(cartItem.itemId);
|
||||||
|
if (!item) continue;
|
||||||
|
const actualQuantity = cartItem.quantity * item.amount;
|
||||||
|
total += actualQuantity;
|
||||||
|
collected.push({
|
||||||
|
id: item.id,
|
||||||
|
name: item.item_name,
|
||||||
|
actualQuantity,
|
||||||
|
itemAmount: item.amount,
|
||||||
|
stock: item.item.stock,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCartItems(collected);
|
||||||
|
setTotalQuantity(total);
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetchItems();
|
||||||
|
}, [userData]);
|
||||||
|
|
||||||
|
// Remove item function (negative quantity)
|
||||||
|
const removeItem = async (id: string, quantity: number) => {
|
||||||
|
try {
|
||||||
|
await fetch("/api/cart", {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id, quantity: -quantity }),
|
||||||
|
});
|
||||||
|
await reloadUser();
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to remove item", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Drag/hold logic
|
||||||
|
const handleMouseDown = (e: React.MouseEvent, item: CartViewItem) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setSliderActive(item.id);
|
||||||
|
setDragging({
|
||||||
|
itemId: item.id,
|
||||||
|
quantity: 1,
|
||||||
|
max: item.actualQuantity / item.itemAmount,
|
||||||
|
rect,
|
||||||
|
});
|
||||||
|
}, 300); // hold threshold
|
||||||
|
setHoldTimeout(timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = (e: React.MouseEvent, item: CartViewItem) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (holdTimeout) {
|
||||||
|
clearTimeout(holdTimeout);
|
||||||
|
setHoldTimeout(null);
|
||||||
|
}
|
||||||
|
if (!sliderActive) {
|
||||||
|
void removeItem(item.id, 1); // quick click removes 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (!dragging) return;
|
||||||
|
const { rect, max } = dragging;
|
||||||
|
let relativeX = e.clientX - rect.left;
|
||||||
|
relativeX = Math.max(0, Math.min(relativeX, rect.width));
|
||||||
|
const quantity = Math.max(1, Math.round((relativeX / rect.width) * max));
|
||||||
|
setDragging({ ...dragging, quantity });
|
||||||
|
},
|
||||||
|
[dragging],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseRelease = useCallback(async () => {
|
||||||
|
if (dragging) {
|
||||||
|
const { itemId, quantity } = dragging;
|
||||||
|
setDragging(null);
|
||||||
|
setSliderActive(null);
|
||||||
|
await removeItem(itemId, quantity);
|
||||||
|
}
|
||||||
|
}, [dragging]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dragging) return;
|
||||||
|
window.addEventListener("mousemove", handleMouseMove);
|
||||||
|
window.addEventListener("mouseup", handleMouseRelease);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousemove", handleMouseMove);
|
||||||
|
window.removeEventListener("mouseup", handleMouseRelease);
|
||||||
|
};
|
||||||
|
}, [dragging, handleMouseMove, handleMouseRelease]);
|
||||||
|
|
||||||
|
const buyDisabled = cartItems.some(
|
||||||
|
(item) => item.stock < item.actualQuantity,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* CART BUTTON */}
|
||||||
|
<button
|
||||||
|
className="relative rounded-full bg-white/10 px-4 py-2 font-semibold transition hover:bg-white/20"
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
>
|
||||||
|
🛒 Cart
|
||||||
|
{totalQuantity > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-bold text-white">
|
||||||
|
{totalQuantity}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* OVERLAY */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative z-10 w-full max-w-md rounded-xl bg-neutral-900 p-6 text-white shadow-xl">
|
||||||
|
<h2 className="mb-4 text-xl font-bold">Your Cart</h2>
|
||||||
|
|
||||||
|
{cartItems.length === 0 ? (
|
||||||
|
<p className="text-neutral-400">Cart is empty</p>
|
||||||
|
) : (
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{cartItems.map((item) => (
|
||||||
|
<li
|
||||||
|
className="flex items-center justify-between rounded-lg bg-white/5 px-3 py-2"
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<img
|
||||||
|
src={getImage(item.name)}
|
||||||
|
alt={item.name}
|
||||||
|
className="h-8 w-8 rounded-full"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<p className="flex items-center gap-1 font-medium">
|
||||||
|
{formatName(item.name)}
|
||||||
|
{item.actualQuantity > item.stock && (
|
||||||
|
<span className="font-bold text-red-500">!</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-neutral-400">
|
||||||
|
Quantity: {item.actualQuantity} / Stock: {item.stock}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Remove button with hold/slider */}
|
||||||
|
<div className="relative flex flex-col items-end">
|
||||||
|
<button
|
||||||
|
onMouseDown={(e) => handleMouseDown(e, item)}
|
||||||
|
onMouseUp={(e) => handleMouseUp(e, item)}
|
||||||
|
className="rounded bg-red-500 px-3 py-1 text-sm font-semibold hover:bg-red-600"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{sliderActive === item.id && dragging && (
|
||||||
|
<div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center">
|
||||||
|
<div className="relative h-full w-full rounded">
|
||||||
|
<div className="relative h-7 w-full rounded bg-white/20">
|
||||||
|
<div className="absolute top-0 left-0 h-full w-full rounded bg-gray-400" />
|
||||||
|
<div
|
||||||
|
className="absolute top-0 left-0 h-full rounded bg-red-500 transition-transform duration-300"
|
||||||
|
style={{
|
||||||
|
width: `${(dragging.quantity / dragging.max) * 100}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-md absolute top-0 right-2 font-bold text-white">
|
||||||
|
x{dragging.quantity * item.itemAmount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`mt-6 w-full rounded-lg py-3 font-bold text-black ${buyDisabled ? "cursor-not-allowed bg-gray-400" : "bg-green-500 hover:bg-green-600"}`}
|
||||||
|
onClick={() => alert("Dummy buy action")}
|
||||||
|
disabled={buyDisabled}
|
||||||
|
>
|
||||||
|
Buy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
|
||||||
type ApiItem = {
|
type ApiItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -30,34 +30,139 @@ const getImage = (name: string) => {
|
|||||||
return `/textures/${mod}/${item}.png`;
|
return `/textures/${mod}/${item}.png`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Items = ({ query }: { query: string }) => {
|
type ItemsProps = {
|
||||||
|
query: string;
|
||||||
|
reloadUser: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Items = ({ query, reloadUser }: ItemsProps) => {
|
||||||
const [items, setItems] = useState<ApiItem[]>([]);
|
const [items, setItems] = useState<ApiItem[]>([]);
|
||||||
|
const [dragging, setDragging] = useState<{
|
||||||
|
itemId: string;
|
||||||
|
quantity: number;
|
||||||
|
max: number;
|
||||||
|
rect: DOMRect;
|
||||||
|
} | null>(null);
|
||||||
|
const [sliderActive, setSliderActive] = useState<string | null>(null);
|
||||||
|
const [holdTimeout, setHoldTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const loadItems = useCallback(async () => {
|
||||||
const loadItems = async () => {
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/items");
|
const res = await fetch("/api/items", { cache: "no-store" });
|
||||||
const data = (await res.json()) as ApiItem[];
|
const data = (await res.json()) as ApiItem[];
|
||||||
|
|
||||||
setItems(data.filter((item) => item.enabled));
|
setItems(data.filter((item) => item.enabled));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load items", err);
|
console.error("Failed to load items", err);
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void loadItems();
|
||||||
|
const interval = setInterval(() => void loadItems(), 60_000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [loadItems]);
|
||||||
|
|
||||||
|
const buyItem = useCallback(
|
||||||
|
async (id: string, quantity: number) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/cart", {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ id, quantity }),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("Buy failed");
|
||||||
|
|
||||||
|
await reloadUser(); // all components now update
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[reloadUser], // <- include reloadUser as dependency
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle hold to activate slider
|
||||||
|
const handleMouseDown = (e: React.MouseEvent, item: ApiItem) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const button = e.currentTarget.getBoundingClientRect();
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setSliderActive(item.id);
|
||||||
|
setDragging({
|
||||||
|
itemId: item.id,
|
||||||
|
quantity: 1,
|
||||||
|
max: item.item.stock / item.amount,
|
||||||
|
rect: button,
|
||||||
|
});
|
||||||
|
}, 300); // 1 second threshold
|
||||||
|
|
||||||
|
setHoldTimeout(timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
void loadItems();
|
const handleMouseUp = (e: React.MouseEvent, item: ApiItem) => {
|
||||||
}, []);
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (holdTimeout) {
|
||||||
|
clearTimeout(holdTimeout);
|
||||||
|
setHoldTimeout(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sliderActive) {
|
||||||
|
void buyItem(item.id, 1); // quick click
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (!dragging) return;
|
||||||
|
|
||||||
|
const { rect, max } = dragging;
|
||||||
|
let relativeX = e.clientX - rect.left;
|
||||||
|
relativeX = Math.max(0, Math.min(relativeX, rect.width));
|
||||||
|
const quantity = Math.max(1, Math.round((relativeX / rect.width) * max));
|
||||||
|
setDragging({ ...dragging, quantity });
|
||||||
|
},
|
||||||
|
[dragging],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseRelease = useCallback(async () => {
|
||||||
|
if (dragging) {
|
||||||
|
setDragging(null);
|
||||||
|
setSliderActive(null);
|
||||||
|
await buyItem(dragging.itemId, dragging.quantity);
|
||||||
|
}
|
||||||
|
}, [dragging, buyItem]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dragging) return;
|
||||||
|
|
||||||
|
const onMouseMove = handleMouseMove;
|
||||||
|
const onMouseUp = () => {
|
||||||
|
void handleMouseRelease();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", onMouseMove);
|
||||||
|
window.addEventListener("mouseup", onMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousemove", onMouseMove);
|
||||||
|
window.removeEventListener("mouseup", onMouseUp);
|
||||||
|
};
|
||||||
|
}, [dragging, handleMouseMove, handleMouseRelease]);
|
||||||
|
|
||||||
const filteredItems = items.filter((item) =>
|
const filteredItems = items.filter((item) =>
|
||||||
formatName(item.item_name).toLowerCase().includes(query.toLowerCase()),
|
formatName(item.item_name).toLowerCase().includes(query.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<div className="relative">
|
||||||
|
{/* Blur and darken overlay */}
|
||||||
|
|
||||||
|
<div className="relative z-10 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||||
{filteredItems.map((item) => (
|
{filteredItems.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className="flex gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
className="relative flex gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={getImage(item.item_name)}
|
src={getImage(item.item_name)}
|
||||||
@ -66,22 +171,50 @@ const Items = ({ query }: { query: string }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="w-70">
|
<div className="w-70">
|
||||||
<h2 className="text-xl font-bold">{formatName(item.item_name)}</h2>
|
<h2 className="text-xl font-bold">
|
||||||
|
{formatName(item.item_name)}
|
||||||
|
</h2>
|
||||||
<p className="text-sm">{item.item.stock} available</p>
|
<p className="text-sm">{item.item.stock} available</p>
|
||||||
<p className="text-xs text-white/70">Shop: {item.shop.label}</p>
|
<p className="text-xs text-white/70">Shop: {item.shop.label}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-end">
|
<div className="relative flex flex-col items-end">
|
||||||
<button className="rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700">
|
<button
|
||||||
|
onMouseDown={(e) => handleMouseDown(e, item)}
|
||||||
|
onMouseUp={(e) => handleMouseUp(e, item)}
|
||||||
|
className="relative z-10 rounded bg-blue-500 px-4 py-2 font-bold text-white hover:bg-blue-700"
|
||||||
|
>
|
||||||
Buy
|
Buy
|
||||||
</button>
|
</button>
|
||||||
<h3 className="w-fit self-center text-xl font-bold">
|
|
||||||
|
<h3 className="mt-2 w-fit self-center text-xl font-bold">
|
||||||
{item.price}$/{item.amount}
|
{item.price}$/{item.amount}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
{/* Slider on button */}
|
||||||
|
{sliderActive === item.id && dragging && (
|
||||||
|
<div className="pointer-events-none absolute inset-0 z-30 flex items-center justify-center">
|
||||||
|
<div className="relative h-full w-full rounded">
|
||||||
|
<div className="relative h-10 w-full rounded bg-white/20">
|
||||||
|
<div className="absolute top-0 left-0 h-full w-full rounded bg-gray-400" />
|
||||||
|
<div
|
||||||
|
className="absolute top-0 left-0 h-full rounded bg-blue-500 transition-transform duration-300"
|
||||||
|
style={{
|
||||||
|
width: `${(dragging.quantity / dragging.max) * 100}%`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-md absolute top-0 right-2 font-bold text-white">
|
||||||
|
x{dragging.quantity * item.amount}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user