From 09b10ac9006c8278c9b62343953f63ecbb98d623 Mon Sep 17 00:00:00 2001 From: Nathan <70660308+NotARoomba@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:03:33 -0500 Subject: [PATCH] fix bugs and compensation and add in admin tools --- backend/bun.lock | 5 +- backend/dist/index.js | 117 +++++++-- backend/package.json | 2 +- backend/src/lib/scraps.ts | 2 +- backend/src/routes/projects.ts | 28 +- backend/src/routes/shop.ts | 43 ++- frontend/bun.lock | 43 +-- frontend/package.json | 14 +- .../lib/components/AddressSelectModal.svelte | 26 +- .../src/lib/components/ShopItemModal.svelte | 48 ++-- frontend/src/lib/stores.ts | 5 +- frontend/src/routes/admin/shop/+page.svelte | 101 +++++--- frontend/src/routes/orders/+page.svelte | 245 ++++++++++++++++++ frontend/src/routes/orders/+page.ts | 1 + .../src/routes/projects/[id]/+page.svelte | 18 +- .../routes/projects/[id]/view/+page.svelte | 50 +++- frontend/src/routes/refinery/+page.svelte | 2 +- frontend/src/routes/shop/+page.svelte | 108 ++++++-- 18 files changed, 682 insertions(+), 176 deletions(-) create mode 100644 frontend/src/routes/orders/+page.svelte create mode 100644 frontend/src/routes/orders/+page.ts diff --git a/backend/bun.lock b/backend/bun.lock index 9d7f1ab..0a06c28 100644 --- a/backend/bun.lock +++ b/backend/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "app", @@ -161,11 +162,11 @@ "openid-client": ["openid-client@6.8.1", "", { "dependencies": { "jose": "^6.1.0", "oauth4webapi": "^3.8.2" } }, "sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw=="], - "pg": ["pg@8.17.2", "", { "dependencies": { "pg-connection-string": "^2.10.1", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw=="], + "pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="], "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], - "pg-connection-string": ["pg-connection-string@2.10.1", "", {}, "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw=="], + "pg-connection-string": ["pg-connection-string@2.11.0", "", {}, "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="], "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], diff --git a/backend/dist/index.js b/backend/dist/index.js index 289aac8..08140e5 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -1819,8 +1819,8 @@ function mergeArrays(chunks) { var import_debug, debug, syncBufferSize, ddSignatureArray, eocdSignatureBytes; var init_ZipHandler = __esm(() => { init_lib3(); - import_debug = __toESM(require_src(), 1); init_ZipToken(); + import_debug = __toESM(require_src(), 1); debug = import_debug.default("tokenizer:inflate"); syncBufferSize = 256 * 1024; ddSignatureArray = signatureToArray(Signature.DataDescriptor); @@ -5848,7 +5848,6 @@ var require_type_overrides = __commonJS((exports, module) => { // node_modules/pg-connection-string/index.js var require_pg_connection_string = __commonJS((exports, module) => { - var { emitWarning } = __require("process"); function parse2(str, options = {}) { if (str.charAt(0) === "/") { const config3 = str.split(" "); @@ -6007,9 +6006,9 @@ var require_pg_connection_string = __commonJS((exports, module) => { return toClientConfig(parse2(str)); } function deprecatedSslModeWarning(sslmode) { - if (!deprecatedSslModeWarning.warned) { + if (!deprecatedSslModeWarning.warned && typeof process !== "undefined" && process.emitWarning) { deprecatedSslModeWarning.warned = true; - emitWarning(`SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'. + process.emitWarning(`SECURITY WARNING: The SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'. In the next major version (pg-connection-string v3.0.0 and pg v9.0.0), these modes will adopt standard libpq semantics, which have weaker security guarantees. To prepare for this change: @@ -7917,7 +7916,7 @@ var require_client = __commonJS((exports, module) => { if (error) { reject(error); } else { - resolve(); + resolve(this); } }); }); @@ -8073,16 +8072,40 @@ var require_client = __commonJS((exports, module) => { activeQuery.handleError(msg, this.connection); } _handleRowDescription(msg) { - this._getActiveQuery().handleRowDescription(msg); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected rowDescription message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handleRowDescription(msg); } _handleDataRow(msg) { - this._getActiveQuery().handleDataRow(msg); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected dataRow message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handleDataRow(msg); } _handlePortalSuspended(msg) { - this._getActiveQuery().handlePortalSuspended(this.connection); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected portalSuspended message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handlePortalSuspended(this.connection); } _handleEmptyQuery(msg) { - this._getActiveQuery().handleEmptyQuery(this.connection); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected emptyQuery message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handleEmptyQuery(this.connection); } _handleCommandComplete(msg) { const activeQuery = this._getActiveQuery(); @@ -8105,10 +8128,22 @@ var require_client = __commonJS((exports, module) => { } } _handleCopyInResponse(msg) { - this._getActiveQuery().handleCopyInResponse(this.connection); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected copyInResponse message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handleCopyInResponse(this.connection); } _handleCopyData(msg) { - this._getActiveQuery().handleCopyData(msg, this.connection); + const activeQuery = this._getActiveQuery(); + if (activeQuery == null) { + const error = new Error("Received unexpected copyData message from backend."); + this._handleErrorEvent(error); + return; + } + activeQuery.handleCopyData(msg, this.connection); } _handleNotification(msg) { this.emit("notification", msg); @@ -8902,7 +8937,7 @@ var require_client2 = __commonJS((exports, module) => { }); self.emit("connect"); self._pulseQueryQueue(true); - cb(); + cb(null, this); }); }); }; @@ -8916,7 +8951,7 @@ var require_client2 = __commonJS((exports, module) => { if (error) { reject(error); } else { - resolve(); + resolve(this); } }); }); @@ -28575,16 +28610,24 @@ projects.get("/:id", async ({ params, headers }) => { reviewer: reviewers.find((rv) => rv.id === r.reviewerId) || null }); } - const submissions = await db.select({ + const activityEntries = await db.select({ id: activityTable.id, action: activityTable.action, createdAt: activityTable.createdAt - }).from(activityTable).where(and(eq(activityTable.projectId, parseInt(params.id)), eq(activityTable.action, "project_submitted"))); - for (const s of submissions) { - activity.push({ - type: "submitted", - createdAt: s.createdAt - }); + }).from(activityTable).where(and(eq(activityTable.projectId, parseInt(params.id)), or(eq(activityTable.action, "project_submitted"), sql`${activityTable.action} LIKE 'earned % scraps'`))); + for (const entry of activityEntries) { + if (entry.action === "project_submitted") { + activity.push({ + type: "submitted", + createdAt: entry.createdAt + }); + } else if (entry.action.startsWith("earned ") && entry.action.endsWith(" scraps")) { + activity.push({ + type: "scraps_earned", + action: entry.action, + createdAt: entry.createdAt + }); + } } activity.push({ type: "created", @@ -28608,6 +28651,7 @@ projects.get("/:id", async ({ params, headers }) => { hours: project[0].hoursOverride ?? project[0].hours, hoursOverride: isOwner ? project[0].hoursOverride : undefined, status: project[0].status, + scrapsAwarded: project[0].scrapsAwarded, createdAt: project[0].createdAt, updatedAt: project[0].updatedAt }, @@ -28903,7 +28947,7 @@ var shopPenaltiesTable = pgTable("shop_penalties", { // src/lib/scraps.ts var PHI = (1 + Math.sqrt(5)) / 2; -var MULTIPLIER = 120; +var MULTIPLIER = 10; function calculateScrapsFromHours(hours) { return Math.floor(hours * PHI * MULTIPLIER); } @@ -29159,6 +29203,7 @@ shop.get("/items", async ({ headers }) => { baseProbability: shopItemsTable.baseProbability, baseUpgradeCost: shopItemsTable.baseUpgradeCost, costMultiplier: shopItemsTable.costMultiplier, + boostAmount: shopItemsTable.boostAmount, createdAt: shopItemsTable.createdAt, updatedAt: shopItemsTable.updatedAt, heartCount: sql`(SELECT COUNT(*) FROM shop_hearts WHERE shop_item_id = ${shopItemsTable.id})`.as("heart_count") @@ -29212,6 +29257,7 @@ shop.get("/items/:id", async ({ params, headers }) => { baseProbability: shopItemsTable.baseProbability, baseUpgradeCost: shopItemsTable.baseUpgradeCost, costMultiplier: shopItemsTable.costMultiplier, + boostAmount: shopItemsTable.boostAmount, createdAt: shopItemsTable.createdAt, updatedAt: shopItemsTable.updatedAt, heartCount: sql`(SELECT COUNT(*) FROM shop_hearts WHERE shop_item_id = ${shopItemsTable.id})`.as("heart_count") @@ -29272,7 +29318,9 @@ shop.post("/items/:id/heart", async ({ params, headers }) => { SELECT EXISTS(SELECT 1 FROM ins) AS hearted `); const hearted = result.rows[0]?.hearted ?? false; - return { hearted }; + const countResult = await db.select({ count: sql`COUNT(*)` }).from(shopHeartsTable).where(eq(shopHeartsTable.shopItemId, itemId)); + const heartCount = Number(countResult[0]?.count) || 0; + return { hearted, heartCount }; }); shop.get("/categories", async () => { const result = await db.selectDistinct({ category: shopItemsTable.category }).from(shopItemsTable); @@ -29366,6 +29414,9 @@ shop.get("/orders", async ({ headers }) => { pricePerItem: shopOrdersTable.pricePerItem, totalPrice: shopOrdersTable.totalPrice, status: shopOrdersTable.status, + orderType: shopOrdersTable.orderType, + shippingAddress: shopOrdersTable.shippingAddress, + isFulfilled: shopOrdersTable.isFulfilled, createdAt: shopOrdersTable.createdAt, itemId: shopItemsTable.id, itemName: shopItemsTable.name, @@ -29451,12 +29502,23 @@ shop.post("/items/:id/try-luck", async ({ params, headers }) => { } return { won: true, orderId: newOrder[0].id, effectiveProbability, rolled }; } - return { won: false, effectiveProbability, rolled }; + const consolationOrder = await tx.insert(shopOrdersTable).values({ + userId: user2.id, + shopItemId: itemId, + quantity: 1, + pricePerItem: item.price, + totalPrice: item.price, + shippingAddress: null, + status: "pending", + orderType: "consolation", + notes: `Consolation scrap paper - rolled ${rolled}, needed ${effectiveProbability} or less` + }).returning(); + return { won: false, effectiveProbability, rolled, consolationOrderId: consolationOrder[0].id }; }); if (result.won) { return { success: true, won: true, orderId: result.orderId, effectiveProbability: result.effectiveProbability, rolled: result.rolled, refineryReset: true, probabilityHalved: true }; } - return { success: true, won: false, effectiveProbability: result.effectiveProbability, rolled: result.rolled }; + return { success: true, won: false, consolationOrderId: result.consolationOrderId, effectiveProbability: result.effectiveProbability, rolled: result.rolled }; } catch (e) { const err = e; if (err.type === "insufficient_funds") { @@ -29502,15 +29564,16 @@ shop.post("/items/:id/upgrade-probability", async ({ params, headers }) => { const { balance } = await getUserScrapsBalance(user2.id, tx); throw { type: "insufficient_funds", balance, cost }; } - const newBoost = currentBoost + 1; + const boostAmount = item.boostAmount; + const newBoost = currentBoost + boostAmount; await tx.insert(refineryOrdersTable).values({ userId: user2.id, shopItemId: itemId, cost, - boostAmount: 1 + boostAmount }); const nextCost = newBoost >= maxBoost ? null : Math.floor(item.baseUpgradeCost * Math.pow(item.costMultiplier / 100, newBoost)); - return { boostPercent: newBoost, nextCost, effectiveProbability: Math.min(adjustedBaseProbability + newBoost, 100) }; + return { boostPercent: newBoost, boostAmount, nextCost, effectiveProbability: Math.min(adjustedBaseProbability + newBoost, 100) }; }); return result; } catch (e) { diff --git a/backend/package.json b/backend/package.json index 10e3850..11d7e93 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,7 +13,7 @@ "drizzle-orm": "^0.45.1", "elysia": "latest", "openid-client": "^6.8.1", - "pg": "^8.17.2" + "pg": "^8.18.0" }, "devDependencies": { "@types/pg": "^8.16.0", diff --git a/backend/src/lib/scraps.ts b/backend/src/lib/scraps.ts index c8cbace..4a13e76 100644 --- a/backend/src/lib/scraps.ts +++ b/backend/src/lib/scraps.ts @@ -6,7 +6,7 @@ import { shopOrdersTable, refineryOrdersTable } from '../schemas/shop' import { userBonusesTable } from '../schemas/users' export const PHI = (1 + Math.sqrt(5)) / 2 -export const MULTIPLIER = 120 +export const MULTIPLIER = 10 export function calculateScrapsFromHours(hours: number): number { return Math.floor(hours * PHI * MULTIPLIER) diff --git a/backend/src/routes/projects.ts b/backend/src/routes/projects.ts index fbc5225..743a5fa 100644 --- a/backend/src/routes/projects.ts +++ b/backend/src/routes/projects.ts @@ -151,8 +151,8 @@ projects.get('/:id', async ({ params, headers }) => { }) } - // Fetch submission events from activity table - const submissions = await db + // Fetch submission and scraps earned events from activity table + const activityEntries = await db .select({ id: activityTable.id, action: activityTable.action, @@ -161,14 +161,25 @@ projects.get('/:id', async ({ params, headers }) => { .from(activityTable) .where(and( eq(activityTable.projectId, parseInt(params.id)), - eq(activityTable.action, 'project_submitted') + or( + eq(activityTable.action, 'project_submitted'), + sql`${activityTable.action} LIKE 'earned % scraps'` + ) )) - for (const s of submissions) { - activity.push({ - type: 'submitted', - createdAt: s.createdAt - }) + for (const entry of activityEntries) { + if (entry.action === 'project_submitted') { + activity.push({ + type: 'submitted', + createdAt: entry.createdAt + }) + } else if (entry.action.startsWith('earned ') && entry.action.endsWith(' scraps')) { + activity.push({ + type: 'scraps_earned', + action: entry.action, + createdAt: entry.createdAt + }) + } } // Add "project created" entry @@ -197,6 +208,7 @@ projects.get('/:id', async ({ params, headers }) => { hours: project[0].hoursOverride ?? project[0].hours, hoursOverride: isOwner ? project[0].hoursOverride : undefined, status: project[0].status, + scrapsAwarded: project[0].scrapsAwarded, createdAt: project[0].createdAt, updatedAt: project[0].updatedAt }, diff --git a/backend/src/routes/shop.ts b/backend/src/routes/shop.ts index 8c8f0b4..8f43626 100644 --- a/backend/src/routes/shop.ts +++ b/backend/src/routes/shop.ts @@ -23,6 +23,7 @@ shop.get('/items', async ({ headers }) => { baseProbability: shopItemsTable.baseProbability, baseUpgradeCost: shopItemsTable.baseUpgradeCost, costMultiplier: shopItemsTable.costMultiplier, + boostAmount: shopItemsTable.boostAmount, createdAt: shopItemsTable.createdAt, updatedAt: shopItemsTable.updatedAt, heartCount: sql`(SELECT COUNT(*) FROM shop_hearts WHERE shop_item_id = ${shopItemsTable.id})`.as('heart_count') @@ -96,6 +97,7 @@ shop.get('/items/:id', async ({ params, headers }) => { baseProbability: shopItemsTable.baseProbability, baseUpgradeCost: shopItemsTable.baseUpgradeCost, costMultiplier: shopItemsTable.costMultiplier, + boostAmount: shopItemsTable.boostAmount, createdAt: shopItemsTable.createdAt, updatedAt: shopItemsTable.updatedAt, heartCount: sql`(SELECT COUNT(*) FROM shop_hearts WHERE shop_item_id = ${shopItemsTable.id})`.as('heart_count') @@ -200,7 +202,16 @@ shop.post('/items/:id/heart', async ({ params, headers }) => { `) const hearted = (result.rows[0] as { hearted: boolean })?.hearted ?? false - return { hearted } + + // Get the updated heart count + const countResult = await db + .select({ count: sql`COUNT(*)` }) + .from(shopHeartsTable) + .where(eq(shopHeartsTable.shopItemId, itemId)) + + const heartCount = Number(countResult[0]?.count) || 0 + + return { hearted, heartCount } }) shop.get('/categories', async () => { @@ -336,6 +347,9 @@ shop.get('/orders', async ({ headers }) => { pricePerItem: shopOrdersTable.pricePerItem, totalPrice: shopOrdersTable.totalPrice, status: shopOrdersTable.status, + orderType: shopOrdersTable.orderType, + shippingAddress: shopOrdersTable.shippingAddress, + isFulfilled: shopOrdersTable.isFulfilled, createdAt: shopOrdersTable.createdAt, itemId: shopItemsTable.id, itemName: shopItemsTable.name, @@ -500,13 +514,29 @@ shop.post('/items/:id/try-luck', async ({ params, headers }) => { return { won: true, orderId: newOrder[0].id, effectiveProbability, rolled } } - return { won: false, effectiveProbability, rolled } + // Create consolation order for scrap paper when user loses + const consolationOrder = await tx + .insert(shopOrdersTable) + .values({ + userId: user.id, + shopItemId: itemId, + quantity: 1, + pricePerItem: item.price, + totalPrice: item.price, + shippingAddress: null, + status: 'pending', + orderType: 'consolation', + notes: `Consolation scrap paper - rolled ${rolled}, needed ${effectiveProbability} or less` + }) + .returning() + + return { won: false, effectiveProbability, rolled, consolationOrderId: consolationOrder[0].id } }) if (result.won) { return { success: true, won: true, orderId: result.orderId, effectiveProbability: result.effectiveProbability, rolled: result.rolled, refineryReset: true, probabilityHalved: true } } - return { success: true, won: false, effectiveProbability: result.effectiveProbability, rolled: result.rolled } + return { success: true, won: false, consolationOrderId: result.consolationOrderId, effectiveProbability: result.effectiveProbability, rolled: result.rolled } } catch (e) { const err = e as { type?: string; balance?: number } if (err.type === 'insufficient_funds') { @@ -584,21 +614,22 @@ shop.post('/items/:id/upgrade-probability', async ({ params, headers }) => { throw { type: 'insufficient_funds', balance, cost } } - const newBoost = currentBoost + 1 + const boostAmount = item.boostAmount + const newBoost = currentBoost + boostAmount // Record the refinery order await tx.insert(refineryOrdersTable).values({ userId: user.id, shopItemId: itemId, cost, - boostAmount: 1 + boostAmount }) const nextCost = newBoost >= maxBoost ? null : Math.floor(item.baseUpgradeCost * Math.pow(item.costMultiplier / 100, newBoost)) - return { boostPercent: newBoost, nextCost, effectiveProbability: Math.min(adjustedBaseProbability + newBoost, 100) } + return { boostPercent: newBoost, boostAmount, nextCost, effectiveProbability: Math.min(adjustedBaseProbability + newBoost, 100) } }) return result diff --git a/frontend/bun.lock b/frontend/bun.lock index bcf7a0c..2e97593 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "scraps", @@ -104,13 +105,13 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/compat": ["@eslint/compat@2.0.1", "", { "dependencies": { "@eslint/core": "^1.0.1" }, "peerDependencies": { "eslint": "^8.40 || 9" }, "optionalPeers": ["eslint"] }, "sha512-yl/JsgplclzuvGFNqwNYV4XNPhP3l62ZOP9w/47atNAdmDtIFCx6X7CSk/SlWUuBGkT4Et/5+UD+WyvX2iiIWA=="], + "@eslint/compat": ["@eslint/compat@2.0.2", "", { "dependencies": { "@eslint/core": "^1.1.0" }, "peerDependencies": { "eslint": "^8.40 || 9 || 10" }, "optionalPeers": ["eslint"] }, "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg=="], "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - "@eslint/core": ["@eslint/core@1.0.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q=="], + "@eslint/core": ["@eslint/core@1.1.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw=="], "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], @@ -204,7 +205,7 @@ "@sveltejs/adapter-static": ["@sveltejs/adapter-static@3.0.10", "", { "peerDependencies": { "@sveltejs/kit": "^2.0.0" } }, "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew=="], - "@sveltejs/kit": ["@sveltejs/kit@2.50.1", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw=="], + "@sveltejs/kit": ["@sveltejs/kit@2.50.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.6.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^3.0.0", "sirv": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": "^5.3.3", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api", "typescript"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="], @@ -248,29 +249,29 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], + "@types/node": ["@types/node@22.19.8", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA=="], "@types/pg": ["@types/pg@8.16.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/type-utils": "8.53.1", "@typescript-eslint/utils": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.54.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.53.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.1", "@typescript-eslint/types": "^8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1" } }, "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.53.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.53.1", "", {}, "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.53.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.53.1", "@typescript-eslint/tsconfig-utils": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.53.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -386,7 +387,7 @@ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - "globals": ["globals@17.1.0", "", {}, "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw=="], + "globals": ["globals@17.3.0", "", {}, "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -552,7 +553,7 @@ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "set-cookie-parser": ["set-cookie-parser@3.0.1", "", {}, "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -572,9 +573,9 @@ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "svelte": ["svelte@5.48.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.1", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-VPWD+UyoSFZ7Nxix5K/F8yWiKWOiROkLlWYXOZReE0TUycw+58YWB3D6lAKT+57xmN99wRX4H3oZmw0NPy7y3Q=="], + "svelte": ["svelte@5.49.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.2", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-jj95WnbKbXsXXngYj28a4zx8jeZx50CN/J4r0CEeax2pbfdsETv/J1K8V9Hbu3DCXnpHz5qAikICuxEooi7eNQ=="], - "svelte-check": ["svelte-check@4.3.5", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q=="], + "svelte-check": ["svelte-check@4.3.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-uBkz96ElE3G4pt9E1Tw0xvBfIUQkeH794kDQZdAUk795UVMr+NJZpuFSS62vcmO/DuSalK83LyOwhgWq8YGU1Q=="], "svelte-eslint-parser": ["svelte-eslint-parser@1.4.1", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA=="], @@ -594,7 +595,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.53.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.1", "@typescript-eslint/parser": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg=="], + "typescript-eslint": ["typescript-eslint@8.54.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ=="], "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -642,10 +643,14 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/pg/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "better-call/set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "eslint/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], diff --git a/frontend/package.json b/frontend/package.json index b94478f..e3f8b01 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,27 +5,27 @@ "node": "22" }, "devDependencies": { - "@eslint/compat": "^2.0.1", + "@eslint/compat": "^2.0.2", "@eslint/js": "^9.39.2", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.50.1", + "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.1.18", - "@types/node": "^22", + "@types/node": "^22.19.8", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.14.0", - "globals": "^17.1.0", + "globals": "^17.3.0", "prettier": "^3.8.1", "prettier-plugin-svelte": "^3.4.1", "prettier-plugin-tailwindcss": "^0.7.2", - "svelte": "^5.48.2", - "svelte-check": "^4.3.5", + "svelte": "^5.49.1", + "svelte-check": "^4.3.6", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", - "typescript-eslint": "^8.53.1", + "typescript-eslint": "^8.54.0", "vite": "^7.3.1" }, "private": true, diff --git a/frontend/src/lib/components/AddressSelectModal.svelte b/frontend/src/lib/components/AddressSelectModal.svelte index 6acebcc..85c6af9 100644 --- a/frontend/src/lib/components/AddressSelectModal.svelte +++ b/frontend/src/lib/components/AddressSelectModal.svelte @@ -17,16 +17,20 @@ primary: boolean } + import type { Snippet } from 'svelte' + let { orderId, itemName, onClose, - onComplete + onComplete, + header }: { orderId: number itemName: string onClose: () => void onComplete: () => void + header?: Snippet } = $props() let addresses = $state([]) @@ -127,13 +131,17 @@

shipping address

-
-

🎉 congratulations!

-

- you won {itemName}! select your shipping address to receive - it. -

-
+ {#if header} + {@render header()} + {:else} +
+

🎉 congratulations!

+

+ you won {itemName}! select your shipping address to receive + it. +

+
+ {/if} {#if error}
@@ -213,7 +221,7 @@ class="inline-flex items-center gap-1 text-sm text-gray-500 hover:text-black transition-colors" > - manage addresses on hack club + manage addresses on hack club auth {:else}
diff --git a/frontend/src/lib/components/ShopItemModal.svelte b/frontend/src/lib/components/ShopItemModal.svelte index 380c960..e268bb5 100644 --- a/frontend/src/lib/components/ShopItemModal.svelte +++ b/frontend/src/lib/components/ShopItemModal.svelte @@ -29,11 +29,13 @@ let { item, onClose, - onTryLuck + onTryLuck, + onConsolation }: { item: ShopItem onClose: () => void onTryLuck: (orderId: number) => void + onConsolation: (orderId: number, rolled: number, needed: number) => void } = $props() let activeTab = $state<'leaderboard' | 'wishlist' | 'buyers'>('leaderboard') @@ -118,10 +120,11 @@ credentials: 'include' }) if (response.ok) { - localHearted = !localHearted - localHeartCount = localHearted ? localHeartCount + 1 : localHeartCount - 1 + const data = await response.json() + localHearted = data.hearted + localHeartCount = data.heartCount // Sync with the store so the shop page updates - updateShopItemHeart(item.id, localHearted) + updateShopItemHeart(item.id, localHearted, localHeartCount) } } catch (e) { console.error('Failed to toggle heart:', e) @@ -143,12 +146,11 @@ return } + await refreshUserScraps() if (data.won) { - await refreshUserScraps() onTryLuck(data.orderId) } else { - alertType = 'info' - alertMessage = 'Better luck next time! You rolled ' + data.rolled + ' but needed ' + data.effectiveProbability.toFixed(0) + ' or less.' + onConsolation(data.consolationOrderId, data.rolled, Math.floor(data.effectiveProbability)) } } catch (e) { console.error('Failed to try luck:', e) @@ -333,19 +335,25 @@ {/if}
- + {#if item.count === 0} + + sold out + + {:else} + + {/if}
{#if showConfirmation} diff --git a/frontend/src/lib/stores.ts b/frontend/src/lib/stores.ts index 9d14e9c..7279709 100644 --- a/frontend/src/lib/stores.ts +++ b/frontend/src/lib/stores.ts @@ -28,6 +28,7 @@ export interface ShopItem { baseProbability: number baseUpgradeCost: number costMultiplier: number + boostAmount: number userBoostPercent: number effectiveProbability: number } @@ -260,14 +261,14 @@ export function updateProject(id: number, updates: Partial) { ) } -export function updateShopItemHeart(itemId: number, hearted: boolean) { +export function updateShopItemHeart(itemId: number, hearted: boolean, heartCount?: number) { shopItemsStore.update((items) => items.map((item) => { if (item.id === itemId) { return { ...item, userHearted: hearted, - heartCount: hearted ? item.heartCount + 1 : item.heartCount - 1 + heartCount: heartCount ?? (hearted ? item.heartCount + 1 : item.heartCount - 1) } } return item diff --git a/frontend/src/routes/admin/shop/+page.svelte b/frontend/src/routes/admin/shop/+page.svelte index 6d6e78f..7cec0e3 100644 --- a/frontend/src/routes/admin/shop/+page.svelte +++ b/frontend/src/routes/admin/shop/+page.svelte @@ -42,9 +42,22 @@ let formCount = $state(0) let formBaseProbability = $state(50) let formBaseUpgradeCost = $state(10) - let formCostMultiplier = $state(115) + let formCostMultiplier = $state(101) let formBoostAmount = $state(1) + let formMonetaryValue = $state(0) let formError = $state(null) + + const PHI = (1 + Math.sqrt(5)) / 2 + const SCRAPS_PER_HOUR = PHI * 10 + const DOLLARS_PER_HOUR = 5 + const SCRAPS_PER_DOLLAR = SCRAPS_PER_HOUR / DOLLARS_PER_HOUR + + function updateFromMonetary(value: number) { + formMonetaryValue = value + formPrice = Math.round(value * SCRAPS_PER_DOLLAR) + formBaseUpgradeCost = Math.round(formPrice * 0.1) || 1 + formBaseProbability = Math.max(0.1, Math.min(100, Math.round((100 - value * 2) * 10) / 10)) + } let deleteConfirmId = $state(null) onMount(async () => { @@ -79,11 +92,12 @@ formImage = '' formDescription = '' formPrice = 0 + formMonetaryValue = 0 formCategory = '' formCount = 0 formBaseProbability = 50 formBaseUpgradeCost = 10 - formCostMultiplier = 115 + formCostMultiplier = 101 formBoostAmount = 1 formError = null showModal = true @@ -95,6 +109,7 @@ formImage = item.image formDescription = item.description formPrice = item.price + formMonetaryValue = item.price / SCRAPS_PER_DOLLAR formCategory = item.category formCount = item.count formBaseProbability = item.baseProbability @@ -198,6 +213,22 @@ +
+

scraps per hour reference

+
+ {#each [0.8, 1, 1.25, 1.5] as mult} +
+
{mult}x
+
+ + {Math.round(SCRAPS_PER_HOUR * mult)} +
+
${(DOLLARS_PER_HOUR * mult).toFixed(2)}/hr
+
+ {/each} +
+
+ {#if loading}
loading...
{:else if items.length === 0} @@ -215,19 +246,19 @@

{item.name}

{item.description}

+ ${(item.price / SCRAPS_PER_DOLLAR).toFixed(2)} + {item.price} {#each item.category.split(',').map(c => c.trim()).filter(Boolean) as cat} {cat} {/each} {item.count} in stock - {item.baseProbability}% base chance + {item.baseProbability}% - {item.baseUpgradeCost} upgrade cost + +{item.boostAmount ?? 1}%/upgrade - {item.costMultiplier / 100}x multiplier - - +{item.boostAmount ?? 1}% per upgrade + ~{(item.price / SCRAPS_PER_HOUR).toFixed(1)} hrs
@@ -297,17 +328,23 @@ >
+
+ + updateFromMonetary(parseFloat(e.currentTarget.value) || 0)} + min="0" + step="0.01" + class="w-full px-4 py-2 border-2 border-black rounded-lg focus:outline-none focus:border-dashed" + /> +

+ = {formPrice} scraps · {formBaseProbability}% base probability · {formBaseUpgradeCost} upgrade cost · ~{(formPrice / SCRAPS_PER_HOUR).toFixed(1)} hrs +

+
+
-
- - -
-
- -
- - +
+ + +
@@ -338,8 +374,9 @@ id="baseProbability" type="number" bind:value={formBaseProbability} - min="0" + min="0.1" max="100" + step="0.1" class="w-full px-4 py-2 border-2 border-black rounded-lg focus:outline-none focus:border-dashed" />
@@ -352,13 +389,12 @@ min="1" class="w-full px-4 py-2 border-2 border-black rounded-lg focus:outline-none focus:border-dashed" /> -

probability increase per upgrade

- + +

auto-set to 10% of price

@@ -376,7 +413,7 @@ min="100" class="w-full px-4 py-2 border-2 border-black rounded-lg focus:outline-none focus:border-dashed" /> -

115 = 1.15x cost increase per upgrade

+

115 = 1.15x per upgrade

diff --git a/frontend/src/routes/orders/+page.svelte b/frontend/src/routes/orders/+page.svelte new file mode 100644 index 0000000..95b4a3e --- /dev/null +++ b/frontend/src/routes/orders/+page.svelte @@ -0,0 +1,245 @@ + + + + my orders - scraps + + +
+ + + back to shop + + +

my orders

+ + {#if loading} +
loading orders...
+ {:else if error} +
{error}
+ {:else if orders.length === 0} +
+ +

no orders yet

+

try your luck in the shop to get some goodies!

+ + go to shop + +
+ {:else} +
+ {#each orders as order} + {@const StatusIcon = getStatusIcon(order.status, order.isFulfilled)} + {@const isConsolation = order.orderType === 'consolation'} +
+
+ {#if isConsolation} +
+ +
+ {:else} + {order.itemName} + {/if} +
+
+
+ {#if isConsolation} +

paper scraps

+

{order.itemName}

+ {:else} +

{order.itemName}

+ {/if} +

+ {getOrderTypeLabel(order.orderType)} · {formatDate(order.createdAt)} +

+
+ + + {getStatusLabel(order.status, order.isFulfilled)} + +
+ +
+ + {order.totalPrice} scraps + + {#if order.quantity > 1} + qty: {order.quantity} + {/if} +
+ + {#if order.shippingAddress} +
+ + {formatAddress(order.shippingAddress)} +
+ {:else if !order.isFulfilled} +

+ ⚠️ no shipping address provided +

+ {/if} +
+
+
+ {/each} +
+ {/if} +
diff --git a/frontend/src/routes/orders/+page.ts b/frontend/src/routes/orders/+page.ts new file mode 100644 index 0000000..62ad4e4 --- /dev/null +++ b/frontend/src/routes/orders/+page.ts @@ -0,0 +1 @@ +export const ssr = false diff --git a/frontend/src/routes/projects/[id]/+page.svelte b/frontend/src/routes/projects/[id]/+page.svelte index 5915f39..4f447cc 100644 --- a/frontend/src/routes/projects/[id]/+page.svelte +++ b/frontend/src/routes/projects/[id]/+page.svelte @@ -1,7 +1,7 @@