Compare commits
8
Commits
f5c581000e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf813acfa6 | ||
|
|
1b867857b8 | ||
|
|
5e2eaff7c4 | ||
|
|
992c9a2e63 | ||
|
|
eef355a791 | ||
|
|
8b4eaee510 | ||
|
|
f588672894 | ||
|
|
9949ad9385 |
+4
-1
@@ -43,4 +43,7 @@ yarn-error.log*
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# idea files
|
# idea files
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
cron_update.log
|
||||||
|
app.pid
|
||||||
File diff suppressed because one or more lines are too long
Vendored
+8
-2
@@ -12274,15 +12274,16 @@ export namespace Prisma {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type CartItemWhereUniqueInput = Prisma.AtLeast<{
|
export type CartItemWhereUniqueInput = Prisma.AtLeast<{
|
||||||
itemId?: string
|
cartId_itemId?: CartItemCartIdItemIdCompoundUniqueInput
|
||||||
AND?: CartItemWhereInput | CartItemWhereInput[]
|
AND?: CartItemWhereInput | CartItemWhereInput[]
|
||||||
OR?: CartItemWhereInput[]
|
OR?: CartItemWhereInput[]
|
||||||
NOT?: CartItemWhereInput | CartItemWhereInput[]
|
NOT?: CartItemWhereInput | CartItemWhereInput[]
|
||||||
|
itemId?: StringFilter<"CartItem"> | string
|
||||||
quantity?: IntFilter<"CartItem"> | number
|
quantity?: IntFilter<"CartItem"> | number
|
||||||
cartId?: StringFilter<"CartItem"> | string
|
cartId?: StringFilter<"CartItem"> | string
|
||||||
cart?: XOR<CartScalarRelationFilter, CartWhereInput>
|
cart?: XOR<CartScalarRelationFilter, CartWhereInput>
|
||||||
sellable?: XOR<SellableScalarRelationFilter, SellableWhereInput>
|
sellable?: XOR<SellableScalarRelationFilter, SellableWhereInput>
|
||||||
}, "itemId">
|
}, "cartId_itemId">
|
||||||
|
|
||||||
export type CartItemOrderByWithAggregationInput = {
|
export type CartItemOrderByWithAggregationInput = {
|
||||||
itemId?: SortOrder
|
itemId?: SortOrder
|
||||||
@@ -13532,6 +13533,11 @@ export namespace Prisma {
|
|||||||
search: string
|
search: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CartItemCartIdItemIdCompoundUniqueInput = {
|
||||||
|
cartId: string
|
||||||
|
itemId: string
|
||||||
|
}
|
||||||
|
|
||||||
export type CartItemCountOrderByAggregateInput = {
|
export type CartItemCountOrderByAggregateInput = {
|
||||||
itemId?: SortOrder
|
itemId?: SortOrder
|
||||||
quantity?: SortOrder
|
quantity?: SortOrder
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-60cecad7f5344b2ab216d0c215477a4a9f0fb3ec9e5201c830e0c4ac3caafb77",
|
"name": "prisma-client-7f3601640bc9191b9770c2fe70db074b0364e5d01ec2def94d81cee0c9a18abc",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "default.js",
|
"browser": "default.js",
|
||||||
|
|||||||
@@ -117,12 +117,14 @@ model Cart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model CartItem {
|
model CartItem {
|
||||||
itemId String @id
|
itemId String
|
||||||
quantity Int
|
quantity Int
|
||||||
cartId String
|
cartId String
|
||||||
|
|
||||||
cart Cart @relation(fields: [cartId], references: [userId], 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)
|
||||||
|
|
||||||
|
@@id([cartId, itemId])
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ const config = {
|
|||||||
"value": "prisma-client-js"
|
"value": "prisma-client-js"
|
||||||
},
|
},
|
||||||
"output": {
|
"output": {
|
||||||
"value": "/var/home/zaremate/Documents/cc-create-shop/generated/prisma",
|
"value": "/var/mnt/f5a90b51-c040-4601-a32c-9629866f15a2/Documents/cc-create-shop/generated/prisma",
|
||||||
"fromEnvVar": null
|
"fromEnvVar": null
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
@@ -277,7 +277,7 @@ const config = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
"sourceFilePath": "/var/home/zaremate/Documents/cc-create-shop/prisma/schema.prisma",
|
"sourceFilePath": "/var/mnt/f5a90b51-c040-4601-a32c-9629866f15a2/Documents/cc-create-shop/prisma/schema.prisma",
|
||||||
"isCustomOutput": true
|
"isCustomOutput": true
|
||||||
},
|
},
|
||||||
"relativeEnvPaths": {
|
"relativeEnvPaths": {
|
||||||
@@ -299,8 +299,8 @@ const config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"mysql\"\n // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below\n // Further reading:\n // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema\n // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string\n url = env(\"DATABASE_URL\")\n}\n\n// Necessary for Next auth\nmodel Account {\n id String @id @default(cuid())\n userId String\n type String\n provider String\n providerAccountId String\n refresh_token String? @db.Text\n access_token String? // @db.Text\n expires_at Int?\n token_type String?\n scope String?\n id_token String? // @db.Text\n session_state String?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n refresh_token_expires_in Int?\n\n @@unique([provider, providerAccountId])\n}\n\nmodel Session {\n id String @id @default(cuid())\n sessionToken String @unique\n userId String\n expires DateTime\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n emailVerified DateTime?\n image String?\n balance Float @default(1000)\n\n accounts Account[]\n sessions Session[]\n shops Shop[]\n carts Cart[]\n adresses Adress[]\n}\n\nmodel VerificationToken {\n identifier String\n token String @unique\n expires DateTime\n\n @@unique([identifier, token])\n}\n\n//////////////////////\n// SHOP\n//////////////////////\n\nmodel Shop {\n id Int @id\n userId String\n label String\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n items Item[]\n sellables Sellable[]\n}\n\nmodel Item {\n item_name String\n shopId Int\n stock Int\n\n shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n sellables Sellable[]\n\n @@id([item_name, shopId])\n}\n\nmodel Sellable {\n id String @id @default(cuid())\n item_name String\n shopId Int\n amount Int\n price Float\n enabled Boolean @default(true)\n\n shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)\n item Item @relation(fields: [item_name, shopId], references: [item_name, shopId], onDelete: Cascade)\n\n cartItems CartItem[]\n}\n\n//////////////////////\n// CART\n//////////////////////\n\nmodel Cart {\n userId String @id\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n cartItems CartItem[]\n}\n\nmodel CartItem {\n itemId String @id\n quantity Int\n cartId String\n\n cart Cart @relation(fields: [cartId], references: [userId], onDelete: Cascade, onUpdate: Cascade)\n sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n}\n\n//////////////////////\n// ADDRESS\n//////////////////////\n\nmodel Adress {\n id String @id @default(cuid())\n userId String\n adress String\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n}\n",
|
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"mysql\"\n // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below\n // Further reading:\n // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema\n // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string\n url = env(\"DATABASE_URL\")\n}\n\n// Necessary for Next auth\nmodel Account {\n id String @id @default(cuid())\n userId String\n type String\n provider String\n providerAccountId String\n refresh_token String? @db.Text\n access_token String? // @db.Text\n expires_at Int?\n token_type String?\n scope String?\n id_token String? // @db.Text\n session_state String?\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n refresh_token_expires_in Int?\n\n @@unique([provider, providerAccountId])\n}\n\nmodel Session {\n id String @id @default(cuid())\n sessionToken String @unique\n userId String\n expires DateTime\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n emailVerified DateTime?\n image String?\n balance Float @default(1000)\n\n accounts Account[]\n sessions Session[]\n shops Shop[]\n carts Cart[]\n adresses Adress[]\n}\n\nmodel VerificationToken {\n identifier String\n token String @unique\n expires DateTime\n\n @@unique([identifier, token])\n}\n\n//////////////////////\n// SHOP\n//////////////////////\n\nmodel Shop {\n id Int @id\n userId String\n label String\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n items Item[]\n sellables Sellable[]\n}\n\nmodel Item {\n item_name String\n shopId Int\n stock Int\n\n shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n sellables Sellable[]\n\n @@id([item_name, shopId])\n}\n\nmodel Sellable {\n id String @id @default(cuid())\n item_name String\n shopId Int\n amount Int\n price Float\n enabled Boolean @default(true)\n\n shop Shop @relation(fields: [shopId], references: [id], onDelete: Cascade)\n item Item @relation(fields: [item_name, shopId], references: [item_name, shopId], onDelete: Cascade)\n\n cartItems CartItem[]\n}\n\n//////////////////////\n// CART\n//////////////////////\n\nmodel Cart {\n userId String @id\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n cartItems CartItem[]\n}\n\nmodel CartItem {\n itemId String\n quantity Int\n cartId String\n\n cart Cart @relation(fields: [cartId], references: [userId], onDelete: Cascade, onUpdate: Cascade)\n sellable Sellable @relation(fields: [itemId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n\n @@id([cartId, itemId])\n}\n\n//////////////////////\n// ADDRESS\n//////////////////////\n\nmodel Adress {\n id String @id @default(cuid())\n userId String\n adress String\n\n user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)\n}\n",
|
||||||
"inlineSchemaHash": "6067e34ba0a13fe49002e5198964784b3870ee91700e9649c3b9ffe64ee9c688",
|
"inlineSchemaHash": "f1f25ffbae59b74d980dd4ecdf5d9929994691bf2809d9a3468ab2985796fd99",
|
||||||
"copyEngine": true
|
"copyEngine": true
|
||||||
}
|
}
|
||||||
config.dirname = '/'
|
config.dirname = '/'
|
||||||
|
|||||||
Generated
+70
-19
@@ -12,9 +12,11 @@
|
|||||||
"@auth/prisma-adapter": "^2.7.2",
|
"@auth/prisma-adapter": "^2.7.2",
|
||||||
"@prisma/client": "^6.6.0",
|
"@prisma/client": "^6.6.0",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
|
"framer-motion": "^12.29.2",
|
||||||
"next": "^15.2.3",
|
"next": "^15.2.3",
|
||||||
"next-auth": "5.0.0-beta.25",
|
"next-auth": "5.0.0-beta.25",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-casino": "^0.2.6",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
@@ -1054,7 +1056,6 @@
|
|||||||
"integrity": "sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==",
|
"integrity": "sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18"
|
"node": ">=18.18"
|
||||||
},
|
},
|
||||||
@@ -1536,7 +1537,6 @@
|
|||||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -1596,7 +1596,6 @@
|
|||||||
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.53.0",
|
"@typescript-eslint/scope-manager": "8.53.0",
|
||||||
"@typescript-eslint/types": "8.53.0",
|
"@typescript-eslint/types": "8.53.0",
|
||||||
@@ -2083,7 +2082,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3063,7 +3061,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3654,6 +3651,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.2.tgz",
|
||||||
|
"integrity": "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.29.2",
|
||||||
|
"motion-utils": "^12.29.2",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -4459,7 +4483,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
@@ -4857,7 +4880,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
@@ -4933,6 +4955,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.2.tgz",
|
||||||
|
"integrity": "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.29.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
|
||||||
|
"integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -5105,7 +5142,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
|
||||||
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
|
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
@@ -5191,7 +5227,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -5514,7 +5549,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||||
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/preact"
|
"url": "https://opencollective.com/preact"
|
||||||
@@ -5545,7 +5579,6 @@
|
|||||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -5656,7 +5689,6 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "6.19.2",
|
"@prisma/config": "6.19.2",
|
||||||
"@prisma/engines": "6.19.2"
|
"@prisma/engines": "6.19.2"
|
||||||
@@ -5680,7 +5712,6 @@
|
|||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
@@ -5752,7 +5783,32 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-casino": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-casino/-/react-casino-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-+ZlU9kV5fBZ6gsyi81Ntd5jWQ17XZwAsIMh4TY+42uN3SZyME00Fu+LuuKS+v8npovvSRK+YKuvoceICdOzVmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^16.8.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-casino/node_modules/react": {
|
||||||
|
"version": "16.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||||
|
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"prop-types": "^15.6.2"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -5762,7 +5818,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -5774,7 +5829,6 @@
|
|||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
@@ -6465,7 +6519,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -6615,7 +6668,6 @@
|
|||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -6852,7 +6904,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -10,23 +10,25 @@
|
|||||||
"db:migrate": "prisma migrate deploy",
|
"db:migrate": "prisma migrate deploy",
|
||||||
"db:push": "prisma db push",
|
"db:push": "prisma db push",
|
||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev --turbo -p 3100",
|
||||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
|
||||||
"postinstall": "prisma generate",
|
"postinstall": "prisma generate",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "next lint --fix",
|
||||||
"preview": "next build && next start",
|
"preview": "next build && next start -p 3100",
|
||||||
"start": "next start",
|
"start": "next start -p 3100",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^2.7.2",
|
"@auth/prisma-adapter": "^2.7.2",
|
||||||
"@prisma/client": "^6.6.0",
|
"@prisma/client": "^6.6.0",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
|
"framer-motion": "^12.29.2",
|
||||||
"next": "^15.2.3",
|
"next": "^15.2.3",
|
||||||
"next-auth": "5.0.0-beta.25",
|
"next-auth": "5.0.0-beta.25",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
"react-casino": "^0.2.6",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
@@ -50,4 +52,4 @@
|
|||||||
"initVersion": "7.40.0"
|
"initVersion": "7.40.0"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@11.6.2"
|
"packageManager": "npm@11.6.2"
|
||||||
}
|
}
|
||||||
@@ -117,12 +117,14 @@ model Cart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model CartItem {
|
model CartItem {
|
||||||
itemId String @id
|
itemId String
|
||||||
quantity Int
|
quantity Int
|
||||||
cartId String
|
cartId String
|
||||||
|
|
||||||
cart Cart @relation(fields: [cartId], references: [userId], 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)
|
||||||
|
|
||||||
|
@@id([cartId, itemId])
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
// Fetch all cart items with related sellable and item to get shopId
|
// Fetch all cart items with related sellable and item to get shopId
|
||||||
const cartItemsWithShop = await db.cartItem.findMany({
|
const cartItemsWithShop = await db.cartItem.findMany({
|
||||||
where: { itemId: { in: cart.map((c) => c.id) } },
|
where: {
|
||||||
|
cartId: userId,
|
||||||
|
itemId: { in: cart.map((c) => c.id) },
|
||||||
|
},
|
||||||
include: {
|
include: {
|
||||||
sellable: {
|
sellable: {
|
||||||
include: {
|
include: {
|
||||||
@@ -49,10 +52,27 @@ export async function POST(request: Request) {
|
|||||||
itemsByShop[shopId] ??= []; // initialize if undefined or null
|
itemsByShop[shopId] ??= []; // initialize if undefined or null
|
||||||
itemsByShop[shopId].push({
|
itemsByShop[shopId].push({
|
||||||
id: ci.sellable.item.item_name, // API expects item_name as id
|
id: ci.sellable.item.item_name, // API expects item_name as id
|
||||||
quantity: ci.quantity,
|
quantity: ci.quantity * ci.sellable.amount, // total quantity based on cart item quantity and sellable amount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user has enough balance for all items
|
||||||
|
const totalCost = cartItemsWithShop.reduce((sum, ci) => {
|
||||||
|
return sum + ci.quantity * ci.sellable.price;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const user = await db.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: { balance: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user || user.balance < totalCost) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Insufficient balance" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(itemsByShop);
|
console.log(itemsByShop);
|
||||||
|
|
||||||
// Send requests per shop
|
// Send requests per shop
|
||||||
@@ -79,6 +99,27 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduct the total cost from the user's balance
|
||||||
|
await db.user.update({
|
||||||
|
where: { id: userId },
|
||||||
|
data: { balance: { decrement: totalCost } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add balance to shop owner
|
||||||
|
console.log("Updating shop owner balance for shop:", shopId);
|
||||||
|
const shop = await db.shop.findUnique({
|
||||||
|
where: { id: Number(shopId) },
|
||||||
|
select: { userId: true },
|
||||||
|
});
|
||||||
|
console.log("Shop owner userId:", shop?.userId);
|
||||||
|
console.log("Total cost to add to shop owner:", totalCost);
|
||||||
|
if (shop) {
|
||||||
|
await db.user.update({
|
||||||
|
where: { id: shop.userId },
|
||||||
|
data: { balance: { increment: totalCost } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Delete successfully sent items from cart
|
// Delete successfully sent items from cart
|
||||||
const itemIds = items.map((i) => i.id);
|
const itemIds = items.map((i) => i.id);
|
||||||
await db.cartItem.deleteMany({
|
await db.cartItem.deleteMany({
|
||||||
|
|||||||
+27
-10
@@ -39,29 +39,46 @@ export async function PATCH(request: Request) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check if the item already exists in the cart
|
// Check if the item already exists in the cart
|
||||||
const existingCartItem = await db.cartItem.findUnique({
|
const existingCartItem = await db.cartItem.findFirst({
|
||||||
where: { itemId },
|
where: {
|
||||||
|
cartId: userId,
|
||||||
|
itemId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingCartItem) {
|
if (existingCartItem) {
|
||||||
if (quantity <= 0 && quantity * -1 >= existingCartItem.quantity) {
|
const nextQuantity = existingCartItem.quantity + quantity;
|
||||||
await db.cartItem.delete({
|
|
||||||
where: { itemId },
|
if (nextQuantity <= 0) {
|
||||||
|
await db.cartItem.deleteMany({
|
||||||
|
where: {
|
||||||
|
cartId: userId,
|
||||||
|
itemId,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Update quantity
|
await db.cartItem.updateMany({
|
||||||
await db.cartItem.update({
|
where: {
|
||||||
where: { itemId },
|
cartId: userId,
|
||||||
data: { quantity: existingCartItem.quantity + quantity },
|
itemId,
|
||||||
|
},
|
||||||
|
data: { quantity: nextQuantity },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (quantity < 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Cannot remove an item that is not in the cart" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Add new item
|
// Add new item
|
||||||
await db.cartItem.create({
|
await db.cartItem.create({
|
||||||
data: {
|
data: {
|
||||||
itemId,
|
itemId,
|
||||||
quantity,
|
quantity,
|
||||||
cartId: userId,
|
cartId: cart.userId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ export async function DELETE(request: Request) {
|
|||||||
return NextResponse.json({ error: "Item not found" }, { status: 404 });
|
return NextResponse.json({ error: "Item not found" }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await db.cartItem.deleteMany({
|
||||||
|
where: { itemId: item.id },
|
||||||
|
});
|
||||||
|
|
||||||
await db.sellable.delete({
|
await db.sellable.delete({
|
||||||
where: {
|
where: {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { auth } from "~/server/auth";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { toUserId, amount } = (await request.json()) as {
|
||||||
|
toUserId: string;
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!toUserId || typeof toUserId !== "string") {
|
||||||
|
return NextResponse.json({ error: "Invalid recipient" }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof amount !== "number" || isNaN(amount) || amount <= 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Amount must be a positive number" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromUserId = session.user.id;
|
||||||
|
|
||||||
|
if (fromUserId === toUserId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Cannot transfer to yourself" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [sender, recipient] = await Promise.all([
|
||||||
|
db.user.findUnique({
|
||||||
|
where: { id: fromUserId },
|
||||||
|
select: { id: true, balance: true },
|
||||||
|
}),
|
||||||
|
db.user.findUnique({
|
||||||
|
where: { id: toUserId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!sender) {
|
||||||
|
return NextResponse.json({ error: "Sender not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!recipient) {
|
||||||
|
return NextResponse.json({ error: "Recipient not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender.balance < amount) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Insufficient balance" },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.$transaction([
|
||||||
|
db.user.update({
|
||||||
|
where: { id: fromUserId },
|
||||||
|
data: { balance: { decrement: amount } },
|
||||||
|
}),
|
||||||
|
db.user.update({
|
||||||
|
where: { id: toUserId },
|
||||||
|
data: { balance: { increment: amount } },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, amount, to: recipient.name });
|
||||||
|
}
|
||||||
@@ -141,6 +141,22 @@ export async function POST(request: Request) {
|
|||||||
const shopsToCreate = body.shops.filter((s) => !currentShopIds.has(s.id));
|
const shopsToCreate = body.shops.filter((s) => !currentShopIds.has(s.id));
|
||||||
|
|
||||||
if (shopsToCreate.length > 0) {
|
if (shopsToCreate.length > 0) {
|
||||||
|
const conflictingShops = await db.shop.findMany({
|
||||||
|
where: {
|
||||||
|
id: { in: shopsToCreate.map((s) => s.id) },
|
||||||
|
NOT: { userId: session.user.id },
|
||||||
|
},
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (conflictingShops.length > 0) {
|
||||||
|
const conflictIds = conflictingShops.map((s) => s.id).join(", ");
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: `Shop ID(s) already claimed by another user: ${conflictIds}` },
|
||||||
|
{ status: 409 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await db.shop.createMany({
|
await db.shop.createMany({
|
||||||
data: shopsToCreate.map((s) => ({
|
data: shopsToCreate.map((s) => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { auth } from "~/server/auth";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await db.user.findMany({
|
||||||
|
where: { id: { not: session.user.id } },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(users);
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import CartButton from "~/components/cart";
|
|||||||
import type { Session } from "next-auth";
|
import type { Session } from "next-auth";
|
||||||
import AccountButton from "./account";
|
import AccountButton from "./account";
|
||||||
import SellableItemsButton from "./new_items";
|
import SellableItemsButton from "./new_items";
|
||||||
|
import TransferButton from "./transfer";
|
||||||
|
|
||||||
type UserResponse = {
|
type UserResponse = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -43,9 +44,7 @@ type Props = {
|
|||||||
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 [userData, setUserData] = useState<UserResponse | null>(null);
|
||||||
const [sellableData, setSellableData] = useState<SellableResponse | null>(
|
const [sellableData, setSellableData] = useState<SellableResponse[]>([]);
|
||||||
null,
|
|
||||||
);
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// Fetch /api/user once and store globally here
|
// Fetch /api/user once and store globally here
|
||||||
@@ -68,7 +67,7 @@ export default function HomeClient({ session }: Props) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const res = await fetch("/api/sellable");
|
const res = await fetch("/api/sellable");
|
||||||
if (!res.ok) throw new Error("Failed to fetch sellable");
|
if (!res.ok) throw new Error("Failed to fetch sellable");
|
||||||
const data = (await res.json()) as SellableResponse;
|
const data = (await res.json()) as SellableResponse[];
|
||||||
setSellableData(data);
|
setSellableData(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -104,9 +103,10 @@ export default function HomeClient({ session }: Props) {
|
|||||||
<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 && (
|
{userData && (
|
||||||
<div className="rounded-full bg-white/10 px-4 py-2 font-semibold">
|
<TransferButton
|
||||||
${userData.balance ?? 0}
|
balance={userData.balance ?? 0}
|
||||||
</div>
|
reloadUser={loadUser}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{session?.user && (
|
{session?.user && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default function AccountButton({ loading }: Props) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json();
|
const data = (await res.json()) as UserData;
|
||||||
|
|
||||||
setUserData({
|
setUserData({
|
||||||
adresses: data.adresses ?? [],
|
adresses: data.adresses ?? [],
|
||||||
@@ -83,7 +83,7 @@ export default function AccountButton({ loading }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await saveAll(next);
|
const res = await saveAll(next);
|
||||||
if (!res || !res.ok) {
|
if (!res?.ok) {
|
||||||
console.error("Failed to add address");
|
console.error("Failed to add address");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ export default function AccountButton({ loading }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await saveAll(next);
|
const res = await saveAll(next);
|
||||||
if (!res || !res.ok) {
|
if (!res?.ok) {
|
||||||
console.error("Failed to remove address");
|
console.error("Failed to remove address");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ export default function AccountButton({ loading }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await saveAll(next);
|
const res = await saveAll(next);
|
||||||
if (!res || !res.ok) {
|
if (!res?.ok) {
|
||||||
console.error("Failed to add shop");
|
console.error("Failed to add shop");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ export default function AccountButton({ loading }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await saveAll(next);
|
const res = await saveAll(next);
|
||||||
if (!res || !res.ok) {
|
if (!res?.ok) {
|
||||||
console.error("Failed to remove shop");
|
console.error("Failed to remove shop");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { CartItem } from "generated/prisma";
|
|
||||||
import { IMAGES_MANIFEST } from "next/dist/shared/lib/constants";
|
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
|
|
||||||
type Cart = {
|
type Cart = {
|
||||||
@@ -202,12 +200,12 @@ export default function CartButton({
|
|||||||
[dragging],
|
[dragging],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleMouseRelease = useCallback(async () => {
|
const handleMouseRelease = useCallback(() => {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
const { itemId, quantity } = dragging;
|
const { itemId, quantity } = dragging;
|
||||||
setDragging(null);
|
setDragging(null);
|
||||||
setSliderActive(null);
|
setSliderActive(null);
|
||||||
await removeItem(itemId, quantity);
|
void removeItem(itemId, quantity);
|
||||||
}
|
}
|
||||||
}, [dragging]);
|
}, [dragging]);
|
||||||
|
|
||||||
@@ -370,7 +368,12 @@ export default function CartButton({
|
|||||||
cartItems.length === 0
|
cartItems.length === 0
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
void buyItems(cartItems, selectedAddress);
|
void buyItems(cartItems, selectedAddress).then(async () => {
|
||||||
|
setCartItems([]);
|
||||||
|
setTotalQuantity(0);
|
||||||
|
setIsOpen(false);
|
||||||
|
await reloadUser();
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
disabled={buyDisabled}
|
disabled={buyDisabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -109,10 +109,17 @@ export default function SellableItemsButton({
|
|||||||
const addSellable = async () => {
|
const addSellable = async () => {
|
||||||
if (selectedIndex === null || price === "" || amount === "") return;
|
if (selectedIndex === null || price === "" || amount === "") return;
|
||||||
|
|
||||||
const item = items[selectedIndex];
|
const item = availableItems[selectedIndex];
|
||||||
|
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
|
console.log("Adding sellable", {
|
||||||
|
shopId: item.shop.id,
|
||||||
|
itemId: item.item_name,
|
||||||
|
price,
|
||||||
|
amount,
|
||||||
|
});
|
||||||
|
|
||||||
const res = await fetch("/api/sellable", {
|
const res = await fetch("/api/sellable", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -231,7 +238,7 @@ export default function SellableItemsButton({
|
|||||||
(acc, s) => {
|
(acc, s) => {
|
||||||
if (!userShopIds.has(s.shopId)) return acc;
|
if (!userShopIds.has(s.shopId)) return acc;
|
||||||
acc[s.shopId] ??= [];
|
acc[s.shopId] ??= [];
|
||||||
acc[s.shopId].push(s);
|
acc[s.shopId]!.push(s);
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
@@ -272,7 +279,7 @@ export default function SellableItemsButton({
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="max-h-56 space-y-4 overflow-y-auto rounded-lg pr-1">
|
||||||
{Object.entries(sellablesByShop).map(([shopId, items]) => (
|
{Object.entries(sellablesByShop).map(([shopId, items]) => (
|
||||||
<div key={shopId} className="rounded-lg bg-white/5 p-3">
|
<div key={shopId} className="rounded-lg bg-white/5 p-3">
|
||||||
<h4 className="mb-2 font-semibold">
|
<h4 className="mb-2 font-semibold">
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
balance: number;
|
||||||
|
reloadUser: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function TransferButton({ balance, reloadUser }: Props) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
|
const [toUserId, setToUserId] = useState("");
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [sending, setSending] = useState(false);
|
||||||
|
|
||||||
|
const openModal = async () => {
|
||||||
|
setError(null);
|
||||||
|
setAmount("");
|
||||||
|
setToUserId("");
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/users");
|
||||||
|
if (!res.ok) throw new Error("Failed to load users");
|
||||||
|
const data = (await res.json()) as User[];
|
||||||
|
setUsers(data);
|
||||||
|
if (data[0]) setToUserId(data[0].id);
|
||||||
|
setIsOpen(true);
|
||||||
|
} catch {
|
||||||
|
setError("Could not load users");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const send = async () => {
|
||||||
|
setError(null);
|
||||||
|
const parsed = parseFloat(amount);
|
||||||
|
if (!toUserId) return setError("Select a recipient");
|
||||||
|
if (isNaN(parsed) || parsed <= 0) return setError("Enter a valid amount");
|
||||||
|
if (parsed > balance) return setError("Insufficient balance");
|
||||||
|
|
||||||
|
setSending(true);
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/transfer", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ toUserId, amount: parsed }),
|
||||||
|
});
|
||||||
|
const data = (await res.json()) as { error?: string; to?: string };
|
||||||
|
if (!res.ok) {
|
||||||
|
setError(data.error ?? "Transfer failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
setAmount("");
|
||||||
|
await reloadUser();
|
||||||
|
} catch {
|
||||||
|
setError("Transfer failed");
|
||||||
|
} finally {
|
||||||
|
setSending(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={openModal}
|
||||||
|
className="rounded-full bg-white/10 px-4 py-2 font-semibold transition hover:bg-white/20"
|
||||||
|
>
|
||||||
|
${balance}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
{/* backdrop */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-black/60"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="relative z-10 w-full max-w-sm rounded-xl bg-neutral-900 p-6 text-white shadow-xl">
|
||||||
|
<h2 className="mb-5 text-xl font-bold">Transfer Balance</h2>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="mb-1 block text-sm font-semibold text-white/70">
|
||||||
|
Recipient
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={toUserId}
|
||||||
|
onChange={(e) => setToUserId(e.target.value)}
|
||||||
|
className="w-full rounded bg-white/10 px-3 py-2 text-sm text-white focus:outline-none"
|
||||||
|
>
|
||||||
|
{users.map((u) => (
|
||||||
|
<option key={u.id} value={u.id} className="bg-neutral-800">
|
||||||
|
{u.name ?? u.id}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-5">
|
||||||
|
<label className="mb-1 block text-sm font-semibold text-white/70">
|
||||||
|
Amount{" "}
|
||||||
|
<span className="text-white/40">
|
||||||
|
(your balance: ${balance})
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
step={1}
|
||||||
|
value={amount}
|
||||||
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
|
placeholder="0"
|
||||||
|
className="w-full rounded bg-white/10 px-3 py-2 text-sm focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="mb-3 rounded bg-red-500/20 px-3 py-2 text-sm text-red-300">
|
||||||
|
{error}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={send}
|
||||||
|
disabled={sending}
|
||||||
|
className="flex-1 rounded bg-purple-600 py-2 font-bold transition hover:bg-purple-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{sending ? "Sending…" : "Send"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="rounded bg-white/10 px-4 py-2 font-semibold transition hover:bg-white/20"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
export type Card = {
|
||||||
|
suit: "♠" | "♥" | "♦" | "♣";
|
||||||
|
value: number;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const suits = ["♠", "♥", "♦", "♣"] as const;
|
||||||
|
const labels = [
|
||||||
|
{ label: "A", value: 11 },
|
||||||
|
{ label: "2", value: 2 },
|
||||||
|
{ label: "3", value: 3 },
|
||||||
|
{ label: "4", value: 4 },
|
||||||
|
{ label: "5", value: 5 },
|
||||||
|
{ label: "6", value: 6 },
|
||||||
|
{ label: "7", value: 7 },
|
||||||
|
{ label: "8", value: 8 },
|
||||||
|
{ label: "9", value: 9 },
|
||||||
|
{ label: "10", value: 10 },
|
||||||
|
{ label: "J", value: 10 },
|
||||||
|
{ label: "Q", value: 10 },
|
||||||
|
{ label: "K", value: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function createDeck(): Card[] {
|
||||||
|
return suits.flatMap((suit) => labels.map((l) => ({ suit, ...l })));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shuffle(deck: Card[]) {
|
||||||
|
return deck.sort(() => (crypto.randomInt(0, 2) === 0 ? -1 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateHand(hand: Card[]): number {
|
||||||
|
let total = hand.reduce((sum, c) => sum + c.value, 0);
|
||||||
|
let aces = hand.filter((c) => c.label === "A").length;
|
||||||
|
|
||||||
|
while (total > 21 && aces--) total -= 10;
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user