Merge feature/api
* Added backend capabilities with SQLite3 * Added routes for Next.js backend
This commit is contained in:
parent
7098346b51
commit
99bc7a319f
4
.gitignore
vendored
4
.gitignore
vendored
@ -36,4 +36,6 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
database.db
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM node:16
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
19
README.md
19
README.md
@ -12,12 +12,15 @@ yarn dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
## API Endpoints
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
||||
|
||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
POST `/user/login` check if user exists
|
||||
GET `/user/USER_ID/stakes` get stake event
|
||||
POST `/user/USER_ID/stakes/claim` claim stake
|
||||
POST `/user/USER_ID/stakes/start` start stake
|
||||
GET `/user/USER_ID/bank-account` get balance
|
||||
PUT `/user/USER_ID/bank-account` sell resource
|
||||
GET `/user/USER_ID/inventory-items` get inventory items
|
||||
POST `/user/USER_ID/inventory-items` buy item
|
||||
GET `/user/USER_ID/staking-sources` get staking sources
|
||||
POST `/user/USER_ID/staking-sources` create staking source
|
||||
|
56
config/game-config.json
Normal file
56
config/game-config.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"resources": ["Sollux", "Shadowstone", "Azurium", "Novafor", "Nebulance"],
|
||||
"moons": {
|
||||
"price": 100,
|
||||
"resourceChance": 1.0,
|
||||
"resourceMinStartAmount": 50,
|
||||
"resourceMaxStartAmount": 200
|
||||
},
|
||||
"store": [
|
||||
{
|
||||
"id": "item1",
|
||||
"name": "Drill A",
|
||||
"description": "This is drill A",
|
||||
"price": 250,
|
||||
"claimAmount": 50,
|
||||
"completionTimeInMins": 5,
|
||||
"upgrades": [
|
||||
{ "tier": 1, "price": 200, "claimBoost": 10 },
|
||||
{ "tier": 2, "price": 300, "claimBoost": 20 },
|
||||
{ "tier": 3, "price": 400, "claimBoost": 30 },
|
||||
{ "tier": 4, "price": 500, "claimBoost": 40 },
|
||||
{ "tier": 5, "price": 600, "claimBoost": 50 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "item2",
|
||||
"name": "Drill B",
|
||||
"description": "This is drill B",
|
||||
"price": 250,
|
||||
"claimAmount": 50,
|
||||
"completionTimeInMins": 5,
|
||||
"upgrades": [
|
||||
{ "tier": 1, "price": 200, "claimBoost": 10 },
|
||||
{ "tier": 2, "price": 300, "claimBoost": 20 },
|
||||
{ "tier": 3, "price": 400, "claimBoost": 30 },
|
||||
{ "tier": 4, "price": 500, "claimBoost": 40 },
|
||||
{ "tier": 5, "price": 600, "claimBoost": 50 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "item3",
|
||||
"name": "Drill C",
|
||||
"description": "This is drill C",
|
||||
"price": 250,
|
||||
"claimAmount": 50,
|
||||
"completionTimeInMins": 5,
|
||||
"upgrades": [
|
||||
{ "tier": 1, "price": 200, "claimBoost": 10 },
|
||||
{ "tier": 2, "price": 300, "claimBoost": 20 },
|
||||
{ "tier": 3, "price": 400, "claimBoost": 30 },
|
||||
{ "tier": 4, "price": 500, "claimBoost": 40 },
|
||||
{ "tier": 5, "price": 600, "claimBoost": 50 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
7
db.ts
Normal file
7
db.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import sqlite3 from "sqlite3";
|
||||
import { open } from "sqlite";
|
||||
|
||||
export const dbConnection = open({
|
||||
filename: "database.db",
|
||||
driver: sqlite3.Database,
|
||||
});
|
@ -1,8 +1,16 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const fs = require("fs");
|
||||
|
||||
const gameConfigContent = fs.readFileSync("config/game-config.json", "utf-8");
|
||||
const gameConfig = JSON.parse(gameConfigContent);
|
||||
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
appDir: true,
|
||||
},
|
||||
}
|
||||
env: {
|
||||
gameConfig: gameConfig,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = nextConfig;
|
||||
|
2351
package-lock.json
generated
2351
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/font": "13.1.6",
|
||||
"@solana/web3.js": "^1.73.3",
|
||||
"@types/node": "18.13.0",
|
||||
"@types/react": "18.0.27",
|
||||
"@types/react-dom": "18.0.10",
|
||||
@ -18,6 +19,8 @@
|
||||
"next": "13.1.6",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"sqlite": "^4.1.2",
|
||||
"sqlite3": "^5.1.4",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
42
sql/data.sql
Normal file
42
sql/data.sql
Normal file
@ -0,0 +1,42 @@
|
||||
INSERT INTO users(name) VALUES
|
||||
('Joe'),
|
||||
('Emil'),
|
||||
('Niko'),
|
||||
('Plug'),
|
||||
('Upgrade');
|
||||
|
||||
|
||||
INSERT INTO bank_account(user_id, balance) VALUES (1, 500);
|
||||
|
||||
INSERT INTO resource_account(user_id, resname, balance) VALUES
|
||||
(1, 'Sollux', 100),
|
||||
(1, 'Shadowstone', 100),
|
||||
(1, 'Azurium', 100),
|
||||
(1, 'Novafor', 100),
|
||||
(1, 'Nebulance', 100);
|
||||
|
||||
INSERT INTO staking_source(name, description, user_id, address) VALUES
|
||||
('Selene''s Eye',
|
||||
'Selene''s Eye is a large and mysterious moon, named for its distinctive appearance - a bright, glowing eye that seems to stare out from the void of space',
|
||||
1,
|
||||
'0x1234568');
|
||||
|
||||
INSERT INTO resource_well(source_id, resname, supply) VALUES
|
||||
(1, 'Sollux', 200),
|
||||
(1, 'Shadowstone', 175),
|
||||
(1, 'Azurium', 0),
|
||||
(1, 'Novafor', 25),
|
||||
(1, 'Nebulance', 10);
|
||||
|
||||
|
||||
INSERT INTO inventory_item(user_id, store_item_id) VALUES
|
||||
(1, 'item1'),
|
||||
(1, 'item2');
|
||||
|
||||
|
||||
INSERT INTO staking_event(user_id, well_id, inventory_item_id, duration_in_mins, stake_amount) VALUES
|
||||
(1, 1, 1, 5, 50),
|
||||
(1, 3, 2, 5, 50);
|
||||
|
||||
INSERT INTO claim_event(staking_event_id, claim_amount) VALUES
|
||||
(2, 50);
|
73
sql/queries.sql
Normal file
73
sql/queries.sql
Normal file
@ -0,0 +1,73 @@
|
||||
-- Give extra moon bucks
|
||||
|
||||
UPDATE bank_account SET balance = 100 WHERE user_id = 1;
|
||||
SELECT * FROM bank_account WHERE user_id = 1;
|
||||
|
||||
--
|
||||
SELECT staking_event.id,well_id,staking_event.source_id,
|
||||
inventory_item_id,staking_event.created_at,expiration_at
|
||||
FROM staking_event
|
||||
INNER JOIN resource_well ON resource_well.id = well_id
|
||||
INNER JOIN staking_source on staking_event.source_id = staking_source.id
|
||||
WHERE staking_event.source_id = ? AND staking_source.user_id = ?;
|
||||
|
||||
SELECT name,init_supply
|
||||
FROM resource_well
|
||||
INNER JOIN resource ON resource.id = resource_well.resource_id
|
||||
WHERE source_id = 1;
|
||||
|
||||
SELECT inventory_item.id,store_item_id, COUNT(upgrade_event.id) as upgrades
|
||||
FROM inventory_item
|
||||
LEFT JOIN upgrade_event ON inventory_item.id = upgrade_event.inventory_item_id
|
||||
WHERE inventory_item.user_id = 1
|
||||
GROUP BY inventory_item.id;
|
||||
|
||||
SELECT inventory_item.id,store_item_id
|
||||
FROM inventory_item;
|
||||
|
||||
SELECT staking_event.id,well_id,staking_event.source_id,
|
||||
inventory_item_id,staking_event.created_at,expiration_at
|
||||
FROM staking_event
|
||||
INNER JOIN staking_source on staking_event.source_id = staking_source.id
|
||||
WHERE staking_event.source_id = 4 AND staking_source.user_id = 1;
|
||||
|
||||
SELECT staking_event.id, staking_event.well_id, staking_event.source_id,
|
||||
staking_event.inventory_item_id, staking_event.duration_in_mins,
|
||||
staking_event.created_at
|
||||
FROM staking_event
|
||||
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
|
||||
WHERE staking_event.source_id = 4 AND claim_event.staking_event_id IS NULL;
|
||||
|
||||
UPDATE staking_event SET created_at = '2023-03-16 09:39:37' WHERE id = 3;
|
||||
|
||||
SELECT staking_event.id, staking_source.id as sourceId, resname as resourceType,
|
||||
inventory_item_id, duration_in_mins, stake_amount, staking_event.created_at,
|
||||
CASE WHEN claim_event.staking_event_id IS NULL THEN 1 ELSE 0 END AS unclaimed
|
||||
FROM staking_event
|
||||
INNER JOIN resource_well ON well_id = resource_well.id
|
||||
INNER JOIN staking_source ON source_id = staking_source.id
|
||||
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
|
||||
WHERE staking_event.user_id = 1;
|
||||
|
||||
SELECT resource_account.id, resource_id,resource.name,balance
|
||||
FROM resource_account
|
||||
INNER JOIN resource ON resource_id = resource.id
|
||||
WHERE user_id = 1;
|
||||
|
||||
SELECT staking_source.id as sourceId,resource_well.id as wellId,resname,supply FROM resource_well
|
||||
INNER JOIN staking_source ON staking_source.id = resource_well.source_id
|
||||
WHERE staking_source.user_id = 1;
|
||||
|
||||
SELECT inventory_item.id, tier, store_item_id,
|
||||
CASE WHEN claim_event.staking_event_id IS NULL THEN 0 ELSE 1 END AS staking
|
||||
FROM inventory_item
|
||||
LEFT JOIN staking_event ON inventory_item_id = inventory_item.id
|
||||
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
|
||||
WHERE inventory_item.id = 3;
|
||||
|
||||
SELECT inventory_item.id, tier, store_item_id, staking_event.id as stakeId,
|
||||
staking_event.created_at as stakeTime, duration_in_mins, stake_amount
|
||||
FROM inventory_item
|
||||
LEFT JOIN staking_event ON inventory_item_id = inventory_item.id
|
||||
WHERE inventory_item.store_item_id = 'item3' AND user_id = 1
|
||||
ORDER BY staking_event.created_at DESC;
|
88
sql/tables.sql
Normal file
88
sql/tables.sql
Normal file
@ -0,0 +1,88 @@
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE users (
|
||||
id integer primary key autoincrement,
|
||||
wallet varchar,
|
||||
name varchar(32) not null
|
||||
);
|
||||
|
||||
CREATE TABLE staking_source(
|
||||
id integer primary key autoincrement,
|
||||
name varchar not null,
|
||||
description varchar not null,
|
||||
user_id int not null,
|
||||
address varchar(128) not null,
|
||||
created_at timestamp DEFAULT (current_timestamp || 'Z'),
|
||||
CONSTRAINT fk_user FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE resource_well(
|
||||
id integer primary key autoincrement,
|
||||
resname varchar not null,
|
||||
source_id int not null,
|
||||
supply int not null,
|
||||
CONSTRAINT fk_sid FOREIGN KEY(source_id)
|
||||
REFERENCES staking_source(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE inventory_item(
|
||||
id integer primary key autoincrement,
|
||||
user_id int not null,
|
||||
tier int not null default 0,
|
||||
store_item_id varchar not null unique,
|
||||
created_at timestamp DEFAULT (current_timestamp || 'Z'),
|
||||
CONSTRAINT fk_user FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE upgrade_event(
|
||||
id integer primary key autoincrement,
|
||||
inventory_item_id int not null,
|
||||
created_at timestamp DEFAULT (current_timestamp || 'Z'),
|
||||
CONSTRAINT fk_iid FOREIGN KEY(inventory_item_id)
|
||||
REFERENCES inventory_item(id)
|
||||
);
|
||||
|
||||
CREATE TABLE staking_event(
|
||||
id integer primary key autoincrement,
|
||||
user_id int not null,
|
||||
well_id int not null,
|
||||
inventory_item_id int not null,
|
||||
duration_in_mins int not null,
|
||||
stake_amount int not null,
|
||||
created_at timestamp DEFAULT (current_timestamp || 'Z'),
|
||||
CONSTRAINT fk_user FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
CONSTRAINT fk_wid FOREIGN KEY(well_id)
|
||||
REFERENCES resource_well(id)
|
||||
CONSTRAINT fk_iiid FOREIGN KEY(inventory_item_id)
|
||||
REFERENCES inventory_item(id)
|
||||
);
|
||||
|
||||
CREATE TABLE claim_event(
|
||||
id integer primary key autoincrement,
|
||||
staking_event_id int not null,
|
||||
claim_amount int not null,
|
||||
created_at timestamp DEFAULT (current_timestamp || 'Z'),
|
||||
CONSTRAINT fk_se_id FOREIGN KEY(staking_event_id)
|
||||
REFERENCES staking_event(id)
|
||||
);
|
||||
|
||||
CREATE TABLE bank_account(
|
||||
id integer primary key autoincrement,
|
||||
user_id int not null,
|
||||
balance int not null default 0 CHECK (balance >= 0),
|
||||
CONSTRAINT fk_user FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE resource_account(
|
||||
id integer primary key autoincrement,
|
||||
resname varchar not null,
|
||||
user_id int not null,
|
||||
balance int not null default 0 CHECK (balance >= 0),
|
||||
CONSTRAINT fk_user FOREIGN KEY(user_id)
|
||||
REFERENCES users(id)
|
||||
);
|
@ -1,25 +1,16 @@
|
||||
"use client";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { IBankAccount } from "typings";
|
||||
|
||||
const BankAccount = (props: { bankAccount: IBankAccount }) => {
|
||||
const BankAccount = (props: { account: IBankAccount }) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
props.bankAccount.resourceType.bgColorClass +
|
||||
" bg-gradient-to-br hover:bg-gradient-to-tr rounded-lg p-3"
|
||||
}
|
||||
className={" bg-gradient-to-br hover:bg-gradient-to-tr rounded-lg p-3"}
|
||||
>
|
||||
<div className="text-white">
|
||||
<span
|
||||
className={
|
||||
props.bankAccount.resourceType.fontColorClass + " font-bold"
|
||||
}
|
||||
>
|
||||
{props.bankAccount.resourceType.name}
|
||||
</span>
|
||||
<span className={" font-bold"}>MoonBucks</span>
|
||||
<h3 className="text-2xl font-bold">
|
||||
{props.bankAccount.balance.toLocaleString("en-US", {
|
||||
{props.account.primaryBalance.toLocaleString("en-US", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}{" "}
|
||||
|
@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { IBankAccount } from "typings";
|
||||
import BankAccount from "./BankAccount";
|
||||
import ResourceAccount from "./ResourceAccount";
|
||||
|
||||
const BankAccountsView = (props: {
|
||||
bankAccounts: IBankAccount[];
|
||||
bankAccount: IBankAccount | undefined;
|
||||
setLightBoxIsActive: () => void;
|
||||
}) => {
|
||||
return (
|
||||
@ -22,9 +22,10 @@ const BankAccountsView = (props: {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{props.bankAccounts.map((bankAccount, id) => {
|
||||
return <BankAccount key={id} bankAccount={bankAccount} />;
|
||||
})}
|
||||
{props.bankAccount &&
|
||||
props.bankAccount.resourceAccounts.map((account, id) => {
|
||||
return <ResourceAccount key={id} account={account} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
45
src/app/Components/ErrorPopover.tsx
Normal file
45
src/app/Components/ErrorPopover.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
|
||||
interface ErrorPopoverProps {
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ErrorPopover: React.FC<ErrorPopoverProps> = ({ message, onClose }) => {
|
||||
return (
|
||||
<div className="fixed bottom-2 right-2 flex items-center justify-center z-50">
|
||||
<div className="bg-white border border-red-500 rounded shadow-lg p-6 max-w-md mx-auto">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0 text-red-500">
|
||||
<i className="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-600">Error</h3>
|
||||
<div className="mt-2 text-sm text-gray-700">{message}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="ml-auto bg-transparent border-none text-red-600 p-1 focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorPopover;
|
@ -4,11 +4,10 @@ import CardLayout from "../Layouts/CardLayout";
|
||||
|
||||
const InventoryItem = (props: {
|
||||
inventoryItem: IInventoryItem;
|
||||
inUse: boolean | undefined;
|
||||
handleIncrementTier: (inventoryItem: IInventoryItem) => void;
|
||||
upgradeInventoryItem: (itemId: number) => void;
|
||||
}) => {
|
||||
const getCurrentTier = (index: number) => {
|
||||
return props.inventoryItem.storeItem.tiers[index];
|
||||
return props.inventoryItem.storeItem.upgrades[index].tier;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -16,7 +15,7 @@ const InventoryItem = (props: {
|
||||
<h3 className="text-xl font-bold mb-2">
|
||||
{props.inventoryItem.storeItem.name}{" "}
|
||||
<span className="bg-green-600 rounded-full px-2">
|
||||
{props.inventoryItem.currentTierIndex + 1}
|
||||
{props.inventoryItem.currentTierIndex}
|
||||
</span>
|
||||
</h3>
|
||||
<p className="text-sm">{props.inventoryItem.storeItem.description}</p>
|
||||
@ -24,18 +23,21 @@ const InventoryItem = (props: {
|
||||
<div className="flex-1">
|
||||
<p className="font-bold mt-4">Yield</p>
|
||||
<ul className="list-none">
|
||||
{getCurrentTier(props.inventoryItem.currentTierIndex).tier}
|
||||
{getCurrentTier(props.inventoryItem.currentTierIndex)}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex">
|
||||
{props.inUse ? (
|
||||
{/* { Check if the item is in use } */}
|
||||
{false ? (
|
||||
<button className="bg-slate-400 text-slate-600 px-4 py-2 rounded-lg font-bold w-28 text-center cursor-not-allowed">
|
||||
Upgrade
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => props.handleIncrementTier(props.inventoryItem)}
|
||||
onClick={() =>
|
||||
props.upgradeInventoryItem(props.inventoryItem.id)
|
||||
}
|
||||
className="bg-slate-100 text-slate-900 px-4 py-2 rounded-lg font-bold w-28 text-center"
|
||||
>
|
||||
Upgrade
|
||||
|
@ -5,15 +5,8 @@ import InventoryItem from "./InventoryItem";
|
||||
const InventoryItemView = (props: {
|
||||
stakingSources: IStakingSource[] | null;
|
||||
inventoryItems: IInventoryItem[] | null | undefined;
|
||||
handleIncrementTier: (inventoryItem: IInventoryItem) => void;
|
||||
upgradeInventoryItem: (itemId: number) => void;
|
||||
}) => {
|
||||
const inUse = (inventoryItemId: number) => {
|
||||
return props.stakingSources?.some((source) => {
|
||||
if (!source.inventoryItem) return false;
|
||||
return source.inventoryItem.id == inventoryItemId;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-slate-800 text-white p-4 rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4">Your Inventory</h2>
|
||||
@ -22,8 +15,7 @@ const InventoryItemView = (props: {
|
||||
<InventoryItem
|
||||
key={id}
|
||||
inventoryItem={inventoryItem}
|
||||
handleIncrementTier={props.handleIncrementTier}
|
||||
inUse={inUse(inventoryItem.id)}
|
||||
upgradeInventoryItem={props.upgradeInventoryItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -7,15 +7,13 @@ const ResourceAccount = (props: { account: IResourceAccount }) => {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
resourceToBg(props.account.resourceType) +
|
||||
" bg-gradient-to-br hover:bg-gradient-to-tr rounded-lg p-3 flex-1"
|
||||
resourceToBg(props.account.name) +
|
||||
" bg-gradient-to-br hover:bg-gradient-to-tr rounded-lg p-3"
|
||||
}
|
||||
>
|
||||
<div className="text-white">
|
||||
<span
|
||||
className={resourceToFc(props.account.resourceType) + " font-bold"}
|
||||
>
|
||||
{props.account.resourceType}
|
||||
<span className={resourceToFc(props.account.name) + " font-bold"}>
|
||||
{props.account.name}
|
||||
</span>
|
||||
<h3 className="text-2xl font-bold">
|
||||
{props.account.balance.toLocaleString("en-US", {
|
||||
|
32
src/app/Components/SelectDropdown.tsx
Normal file
32
src/app/Components/SelectDropdown.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useState, ChangeEvent } from "react";
|
||||
import { ISelectDropdownProps } from "typings";
|
||||
|
||||
const SelectDropdown: React.FC<ISelectDropdownProps> = (props) => {
|
||||
const [selectedValue, setSelectedValue] = useState("");
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedValue(event.target.value);
|
||||
if (props.onChange) {
|
||||
props.onChange(event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
value={selectedValue}
|
||||
onChange={handleChange}
|
||||
className="text-black block w-full p-2 border border-gray-300 rounded-lg"
|
||||
>
|
||||
<option value="">Select an option</option>
|
||||
{props.options.map((option, index) => (
|
||||
<option key={index} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectDropdown;
|
@ -1,114 +1,87 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { IInventoryItem, IStakingSource, IClaimableResource } from "typings";
|
||||
import { IInventoryItem, IStakingSource, IStake, IOption } from "typings";
|
||||
import CardLayout from "../Layouts/CardLayout";
|
||||
import {
|
||||
calculateRemainingTime,
|
||||
prettifyTime,
|
||||
getObjectFromArray,
|
||||
} from "../../utils/helpers";
|
||||
import SelectDropdown from "./SelectDropdown";
|
||||
|
||||
const StakingSource = (props: {
|
||||
stakingSource: IStakingSource;
|
||||
inventoryItems: IInventoryItem[] | null | undefined;
|
||||
handleAddItem: (
|
||||
inventoryItem: IInventoryItem,
|
||||
stakingSource: IStakingSource
|
||||
) => void;
|
||||
claimResource: (
|
||||
stakingSource: IStakingSource,
|
||||
claimedResource: IClaimableResource
|
||||
) => boolean;
|
||||
claimStake: (stakingEventId: number) => void;
|
||||
}) => {
|
||||
const [isMining, setIsMining] = useState(false);
|
||||
const [miningTime, setMiningTime] = useState(0);
|
||||
const [claimableResource, setClaimableResource] =
|
||||
useState<IClaimableResource | null>(null);
|
||||
const [activeInventoryItem, setActiveInventoryItem] =
|
||||
useState<IInventoryItem | null>(null);
|
||||
const [activeTier, setActiveTier] = useState(0);
|
||||
const [activeStakes, setActiveStakes] = useState<IStake[]>([]);
|
||||
|
||||
// Check if claimable every second
|
||||
useEffect(() => {
|
||||
if (isMining && activeInventoryItem) {
|
||||
const intervalId = setInterval(() => {
|
||||
setMiningTime((prev) => prev - 1);
|
||||
}, 1000);
|
||||
|
||||
if (miningTime === 0) {
|
||||
setIsMining(false);
|
||||
|
||||
// Get a random resource from available wells
|
||||
let randResource =
|
||||
props.stakingSource.resourceWells[
|
||||
Math.floor(Math.random() * props.stakingSource.resourceWells.length)
|
||||
];
|
||||
|
||||
// Get yield based on the tier
|
||||
const totalYield = activeInventoryItem.storeItem.tiers[activeTier].tier;
|
||||
|
||||
// Construct the claimableResource
|
||||
const claimableResource: IClaimableResource = {
|
||||
resourceType: randResource.resourceType,
|
||||
balance: totalYield,
|
||||
};
|
||||
|
||||
setClaimableResource(claimableResource);
|
||||
clearInterval(intervalId);
|
||||
setMiningTime(activeInventoryItem.storeItem.timeToClaim);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}
|
||||
}, [isMining, miningTime]);
|
||||
|
||||
const isChecked = (item: IInventoryItem) => {
|
||||
if (!props.stakingSource.inventoryItem) return false;
|
||||
return item.id === props.stakingSource.inventoryItem.id;
|
||||
};
|
||||
|
||||
const handleStartMining = () => {
|
||||
if (props.stakingSource.inventoryItem) {
|
||||
setActiveInventoryItem(props.stakingSource.inventoryItem);
|
||||
setMiningTime(props.stakingSource.inventoryItem.storeItem.timeToClaim);
|
||||
setActiveTier(props.stakingSource.inventoryItem.currentTierIndex);
|
||||
setIsMining(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClaim = () => {
|
||||
if (!claimableResource) return;
|
||||
if (props.claimResource(props.stakingSource, claimableResource)) {
|
||||
setClaimableResource(null);
|
||||
}
|
||||
};
|
||||
|
||||
const renderButton = () => {
|
||||
if (props.stakingSource.inventoryItem) {
|
||||
if (!claimableResource && !isMining) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => handleStartMining()}
|
||||
className="bg-slate-100 text-slate-900 px-4 py-2 rounded-lg font-bold text-center"
|
||||
>
|
||||
Start Mining
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isMining)
|
||||
return (
|
||||
<button className="bg-slate-400 text-slate-600 px-4 py-2 rounded-lg font-bold w-28 text-center cursor-not-allowed">
|
||||
{miningTime}
|
||||
</button>
|
||||
const handleIsClaimable = props.stakingSource.activeStakes.map((stake) => {
|
||||
const remainingTime = calculateRemainingTime(
|
||||
stake.startTime,
|
||||
stake.durationInMins
|
||||
);
|
||||
|
||||
if (claimableResource) {
|
||||
const obj = {
|
||||
...stake,
|
||||
remainingTime: remainingTime,
|
||||
isClaimable: remainingTime <= 0,
|
||||
};
|
||||
|
||||
return obj;
|
||||
});
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
setActiveStakes(handleIsClaimable);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
|
||||
const handleStartMining = () => {
|
||||
console.log("Start mining..");
|
||||
};
|
||||
|
||||
const handleClaim = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
console.log("Start claiming..");
|
||||
};
|
||||
|
||||
const handleSelectChange = (selectedValue: string) => {
|
||||
console.log(selectedValue);
|
||||
// Render a staking button
|
||||
};
|
||||
|
||||
const RenderButtonOrCountdown = (props: { stake: IStake }) => {
|
||||
if (!props.stake.remainingTime) return <p>Error</p>;
|
||||
if (!props.stake.unclaimed)
|
||||
return <p className="text-red-400 font-bold">Claimed</p>;
|
||||
|
||||
if (props.stake.remainingTime <= 0) {
|
||||
return (
|
||||
<button
|
||||
onClick={() => handleClaim()}
|
||||
onClick={(e) => handleClaim(e)}
|
||||
className="bg-slate-100 text-slate-900 px-4 py-2 rounded-lg font-bold text-center"
|
||||
>
|
||||
Claim
|
||||
</button>
|
||||
);
|
||||
}
|
||||
if (true) {
|
||||
return (
|
||||
<div className="flex">
|
||||
<p>{prettifyTime(props.stake.remainingTime).hours} hrs.</p>
|
||||
<p>{prettifyTime(props.stake.remainingTime).minutes} mins.</p>
|
||||
<p>{prettifyTime(props.stake.remainingTime).seconds} sec.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const isChecked = (item: IInventoryItem): boolean => {
|
||||
return props.stakingSource.activeStakes.some((obj) => obj.id === item.id);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -117,69 +90,54 @@ const StakingSource = (props: {
|
||||
<p className="text-sm">{props.stakingSource.description}</p>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1">
|
||||
<p className="font-bold mt-4">Resources</p>
|
||||
<ul className="list-none">
|
||||
{props.stakingSource.resourceWells.map((resourceWell, id) => (
|
||||
<li key={id}>
|
||||
<span>{resourceWell.resourceType.name}</span>{" "}
|
||||
<span className="text-teal-500 font-bold">
|
||||
{resourceWell.supply}
|
||||
</span>
|
||||
</li>
|
||||
<p className="font-bold mt-4 mb-2">Stakes</p>
|
||||
{activeStakes &&
|
||||
activeStakes.map((stake, id) => (
|
||||
<div key={id} className="mb-3 border border-white p-2">
|
||||
<p>
|
||||
<span className="font-bold">Drill: </span>
|
||||
{props.inventoryItems &&
|
||||
getObjectFromArray(props.inventoryItems, "id", stake.id)
|
||||
?.storeItem.name}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-bold">Resource: </span>
|
||||
{stake.resourceType}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-bold">Stake amount: </span>
|
||||
{stake.stakeAmount}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-bold">Start Time:</span>{" "}
|
||||
{stake.startTime}
|
||||
</p>
|
||||
<RenderButtonOrCountdown stake={stake} />
|
||||
</div>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-bold mt-4">Status</p>
|
||||
<ul className="list-none">
|
||||
<li className="flex">
|
||||
<span className="text-green-600 mr-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
Operational
|
||||
</li>
|
||||
</ul>
|
||||
<p className="font-bold mt-4">Equipment</p>
|
||||
{!isMining
|
||||
? props.inventoryItems &&
|
||||
props.inventoryItems.map((item, id) => (
|
||||
<div key={id}>
|
||||
<input
|
||||
type="radio"
|
||||
name="inventoryItem"
|
||||
value={item.id}
|
||||
checked={isChecked(item)}
|
||||
onChange={() =>
|
||||
props.handleAddItem(item, props.stakingSource)
|
||||
}
|
||||
/>
|
||||
<label htmlFor={item.id.toString()}>
|
||||
{item.storeItem.name}{" "}
|
||||
<span className="bg-white text-black rounded-full px-2">
|
||||
{item.currentTierIndex + 1}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
: props.stakingSource.inventoryItem && (
|
||||
<p>{props.stakingSource.inventoryItem.storeItem.name}</p>
|
||||
)}
|
||||
<p className="font-bold mt-4 mb-2">Activate Drills</p>
|
||||
{props.inventoryItems &&
|
||||
props.inventoryItems.map(
|
||||
(item, id) =>
|
||||
!isChecked(item) && (
|
||||
<div key={id} className="border border-white p-2">
|
||||
<p>{item.storeItem.name}</p>
|
||||
<SelectDropdown
|
||||
options={props.stakingSource.resourceWells.map(
|
||||
(well): IOption => ({
|
||||
value: well.resourceType,
|
||||
label: well.resourceType,
|
||||
})
|
||||
)}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{renderButton()}
|
||||
</CardLayout>
|
||||
);
|
||||
};
|
||||
|
@ -1,19 +1,12 @@
|
||||
"use client";
|
||||
import React from "react";
|
||||
import { IInventoryItem, IStakingSource, IClaimableResource } from "typings";
|
||||
import { IInventoryItem, IStakingSource, IStake, IGameConfig } from "typings";
|
||||
import StakingSource from "./StakingSource";
|
||||
|
||||
const StakingSourcesView = (props: {
|
||||
stakingSources: IStakingSource[] | null;
|
||||
inventoryItems: IInventoryItem[] | null | undefined;
|
||||
handleAddItem: (
|
||||
inventoryItem: IInventoryItem,
|
||||
stakingSource: IStakingSource
|
||||
) => void;
|
||||
claimResource: (
|
||||
stakingSource: IStakingSource,
|
||||
claimedResource: IClaimableResource
|
||||
) => boolean;
|
||||
claimStake: (stakingEventId: number) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div className="bg-slate-800 p-4 rounded-lg text-white">
|
||||
@ -24,8 +17,7 @@ const StakingSourcesView = (props: {
|
||||
key={id}
|
||||
stakingSource={stakingSource}
|
||||
inventoryItems={props.inventoryItems}
|
||||
handleAddItem={props.handleAddItem}
|
||||
claimResource={props.claimResource}
|
||||
claimStake={props.claimStake}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
import React, { useState } from "react";
|
||||
import { IStoreItem, IInventoryItem } from "typings";
|
||||
import { IStoreItem } from "typings";
|
||||
import CommonCardLayout from "../Layouts/CommonCardLayout";
|
||||
|
||||
const StoreItem = (props: {
|
||||
storeItem: IStoreItem;
|
||||
handleBuy: (storeItem: IStoreItem) => void;
|
||||
buyStoreItem: (itemId: string) => void;
|
||||
owned: boolean | undefined;
|
||||
}) => {
|
||||
return (
|
||||
@ -22,20 +22,22 @@ const StoreItem = (props: {
|
||||
<p className="text-lg font-bold">$ {props.storeItem.price}</p>
|
||||
<p className="font-bold mt-3">Tier Upgrades</p>
|
||||
<table className="table-auto text-center">
|
||||
<tr>
|
||||
<th className="border border-white py-1 px-3 w-18">Tier</th>
|
||||
<th className="border border-white py-1 px-3 w-28">Price</th>
|
||||
</tr>
|
||||
{props.storeItem.tiers.map((tier) => (
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-white py-1 px-3 w-18">
|
||||
{tier.tier}
|
||||
</td>
|
||||
<td className="border border-white py-1 px-3 w-28">
|
||||
${tier.price}
|
||||
</td>
|
||||
<th className="border border-white py-1 px-3 w-18">Tier</th>
|
||||
<th className="border border-white py-1 px-3 w-28">Price</th>
|
||||
</tr>
|
||||
))}
|
||||
{props.storeItem.upgrades.map((tier, id) => (
|
||||
<tr key={id}>
|
||||
<td className="border border-white py-1 px-3 w-18">
|
||||
{tier.tier}
|
||||
</td>
|
||||
<td className="border border-white py-1 px-3 w-28">
|
||||
${tier.price}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex items-center h-100">
|
||||
@ -45,7 +47,7 @@ const StoreItem = (props: {
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => props.handleBuy(props.storeItem)}
|
||||
onClick={() => props.buyStoreItem(props.storeItem.id)}
|
||||
className="bg-slate-100 text-slate-900 px-4 py-2 rounded-lg font-bold w-28 text-center"
|
||||
>
|
||||
Buy
|
||||
|
@ -5,9 +5,9 @@ import StoreItem from "./StoreItem";
|
||||
const StoreItemView = (props: {
|
||||
storeItems: IStoreItem[] | null;
|
||||
inventoryItems: IInventoryItem[] | null | undefined;
|
||||
handleBuy: (storeItem: IStoreItem) => void;
|
||||
buyStoreItem: (itemId: string) => void;
|
||||
}) => {
|
||||
const isOwned = (storeItemId: number) => {
|
||||
const isOwned = (storeItemId: string) => {
|
||||
return props.inventoryItems?.some(
|
||||
(item) => item.storeItem.id == storeItemId
|
||||
);
|
||||
@ -19,7 +19,6 @@ const StoreItemView = (props: {
|
||||
owned: isOwned(storeItem.id),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-slate-800 p-4 rounded-lg">
|
||||
<h2 className="text-2xl font-bold mb-4 text-white">Store</h2>
|
||||
@ -30,7 +29,7 @@ const StoreItemView = (props: {
|
||||
<StoreItem
|
||||
key={id}
|
||||
storeItem={storeItem}
|
||||
handleBuy={props.handleBuy}
|
||||
buyStoreItem={props.buyStoreItem}
|
||||
owned={storeItem.owned}
|
||||
/>
|
||||
))}
|
||||
|
@ -1,14 +1,80 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { PublicKey } from "@solana/web3.js";
|
||||
|
||||
import "./globals.css";
|
||||
import { PhantomProvider } from "typings";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [provider, setProvider] = useState<PhantomProvider>();
|
||||
const [walletAddress, setWalletAddress] = useState("");
|
||||
const [showErrorMessage, setShowErrorMessage] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const provider = getProvider();
|
||||
setProvider(provider);
|
||||
if (!provider) return;
|
||||
provider.on("connect", async (publicKey: PublicKey) => {
|
||||
const body = { publicKey: publicKey };
|
||||
const res = await fetch("/api/user/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (res.status === 200) {
|
||||
setWalletAddress(publicKey.toBase58());
|
||||
}
|
||||
if (res.status === 404) {
|
||||
setShowErrorMessage(true);
|
||||
setTimeout(() => {
|
||||
setShowErrorMessage(false);
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getProvider = () => {
|
||||
if ("phantom" in window) {
|
||||
const anyWindow: any = window;
|
||||
const provider = anyWindow.phantom.solana;
|
||||
if (provider.isPhantom) return provider;
|
||||
}
|
||||
};
|
||||
|
||||
const connectWallet = () => {
|
||||
if (!provider) return console.log("no provider");
|
||||
provider.connect();
|
||||
};
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<head />
|
||||
<body className="bg-slate-900">{children}</body>
|
||||
<body className="bg-slate-900">
|
||||
<nav className="text-white flex justify-between mt-4">
|
||||
<div className="p-2 m-4 bg-slate-800 rounded-lg">Moon Miners</div>
|
||||
{showErrorMessage && (
|
||||
<div className="p-2 m-4 bg-red-800 rounded-lg">
|
||||
Wallet address not registered!
|
||||
</div>
|
||||
)}
|
||||
{walletAddress.length === 0 ? (
|
||||
<button
|
||||
onClick={connectWallet}
|
||||
className="p-2 m-4 bg-slate-800 rounded-lg">
|
||||
Connect Wallet
|
||||
</button>
|
||||
) : (
|
||||
<div className="p-2 m-4 bg-slate-800 rounded-lg">
|
||||
{walletAddress}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
346
src/app/page.tsx
346
src/app/page.tsx
@ -6,12 +6,14 @@ import StakingSourcesView from "./Components/StakingSourcesView";
|
||||
import BankAccountsView from "./Components/BankAccountsView";
|
||||
import StoreItemView from "./Components/StoreItemView";
|
||||
import ResourceStore from "./Components/ResourceStore";
|
||||
import ErrorPopover from "./Components/ErrorPopover";
|
||||
import { gameConfig } from "@/utils/gameLogic";
|
||||
import {
|
||||
IStoreItem,
|
||||
IInventoryItem,
|
||||
IStakingSource,
|
||||
IBankAccount,
|
||||
IClaimableResource,
|
||||
IStake,
|
||||
} from "typings";
|
||||
|
||||
export default function Home() {
|
||||
@ -21,176 +23,220 @@ export default function Home() {
|
||||
const [stakingSources, setStakingSources] = useState<IStakingSource[] | null>(
|
||||
[]
|
||||
);
|
||||
const [bankAccounts, setBankAccounts] = useState<IBankAccount[]>([]);
|
||||
const [bankAccount, setBankAccount] = useState<IBankAccount>();
|
||||
const [storeItems, setStoreItems] = useState<IStoreItem[]>([]);
|
||||
const [userStakes, setUserStakes] = useState<IStake[]>([]);
|
||||
const [lightBoxIsActive, setLightBoxIsActive] = useState(false);
|
||||
const [userId, setUserId] = useState<number | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [errorTime, setErrorTime] = useState(3);
|
||||
|
||||
// EMIL
|
||||
// It should be possible to add infinite drills on each resource (however many the user has)
|
||||
|
||||
// JOE
|
||||
// Transaction table
|
||||
// Add dev buttons to prototype
|
||||
|
||||
// DONE POST /user/login check if user exists
|
||||
// DONE GET /user/USER_ID/stakes get stake event
|
||||
// DONE POST /user/USER_ID/stakes/claim claim stake
|
||||
// DONE POST /user/USER_ID/stakes/start start stake
|
||||
// DONE GET /user/USER_ID/bank-account get balance
|
||||
// DONE PUT /user/USER_ID/bank-account sell resource
|
||||
// DONE GET /user/USER_ID/inventory-items get inventory items
|
||||
// DONE POST /user/USER_ID/inventory-items buy item
|
||||
// DONE GET /user/USER_ID/staking-sources get staking sources
|
||||
// DONE POST /user/USER_ID/staking-sources create staking source
|
||||
|
||||
// Connect to DB here
|
||||
useEffect(() => {
|
||||
// get the user who is currently logged in
|
||||
const loggedInUser = 1;
|
||||
const fetchUser = async (wallet: string) => {
|
||||
const response = await fetch(`/api/user/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
wallet: wallet,
|
||||
}),
|
||||
});
|
||||
if (response.status === 200) {
|
||||
console.log("Logged in");
|
||||
const { userId } = await response.json();
|
||||
setUserId(userId);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchInventoryItems = async () => {
|
||||
const response = await fetch(`/api/user/${loggedInUser}/inventory-items`);
|
||||
const DBInventoryItems = await response.json();
|
||||
setInventoryItems(DBInventoryItems.message);
|
||||
const fetchBankAccount = async () => {
|
||||
const response = await fetch(`/api/user/${userId}/bank-account`);
|
||||
const bankAccount = await response.json();
|
||||
setBankAccount(bankAccount);
|
||||
};
|
||||
|
||||
const fetchStakingSources = async () => {
|
||||
const response = await fetch(`/api/user/${loggedInUser}/staking-sources`);
|
||||
const DBStakingSources = await response.json();
|
||||
setStakingSources(DBStakingSources.message);
|
||||
const response = await fetch(`/api/user/${userId}/staking-sources`);
|
||||
const sources = await response.json();
|
||||
setStakingSources(sources);
|
||||
};
|
||||
|
||||
const fetchBankAccounts = async () => {
|
||||
const response = await fetch(`/api/user/${loggedInUser}/bank-accounts`);
|
||||
const DBBankAccounts = await response.json();
|
||||
setBankAccounts(DBBankAccounts.message);
|
||||
};
|
||||
const fetchInventoryItems = async () => {
|
||||
const response = await fetch(`/api/user/${userId}/inventory-items`);
|
||||
const DBInventoryItems = await response.json();
|
||||
|
||||
const fetchStoreItems = async () => {
|
||||
const response = await fetch(`/api/store/items`);
|
||||
const DBStoreItems = await response.json();
|
||||
setStoreItems(DBStoreItems.message);
|
||||
};
|
||||
|
||||
fetchBankAccounts();
|
||||
fetchStakingSources();
|
||||
fetchInventoryItems();
|
||||
fetchStoreItems();
|
||||
}, []);
|
||||
|
||||
// Use effect to update the items on staking sources when inventoryItems are updated
|
||||
useEffect(() => {
|
||||
const updateItemsOnStakingSources = () => {
|
||||
const updatedStakingSources = stakingSources?.map((source) => {
|
||||
const item = inventoryItems?.find(
|
||||
(item) => source.inventoryItem?.id === item.id
|
||||
for (const invItem of DBInventoryItems) {
|
||||
invItem.storeItem = gameConfig.store.find(
|
||||
(item) => item.id === invItem.storeItemId
|
||||
);
|
||||
if (item) {
|
||||
return { ...source, inventoryItem: item };
|
||||
} else {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
setInventoryItems(DBInventoryItems);
|
||||
};
|
||||
|
||||
const fetchStakes = async () => {
|
||||
const response = await fetch(`/api/user/${userId}/stakes`);
|
||||
const stakes = await response.json();
|
||||
setUserStakes(
|
||||
stakes.map((stake: IStake) => ({
|
||||
...stake,
|
||||
unclaimed: stake.unclaimed == 1,
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
fetchUser("abcdefg"); // Public key goes here
|
||||
// Nico is there a better way of checking if a user is logged in?
|
||||
if (userId) {
|
||||
setStoreItems(gameConfig.store);
|
||||
fetchBankAccount();
|
||||
fetchStakingSources();
|
||||
fetchInventoryItems();
|
||||
fetchStakes();
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
// Hide error automatically
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
const intervalId = setInterval(() => {
|
||||
setErrorTime((prev) => prev - 1);
|
||||
}, 1000);
|
||||
if (errorTime === 0) {
|
||||
setError(null);
|
||||
clearInterval(intervalId);
|
||||
setErrorTime(3);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}
|
||||
}, [error, errorTime]);
|
||||
|
||||
const claimStake = async (stakingEventId: number) => {
|
||||
const response = await fetch(`/api/user/${userId}/stakes/claim`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ stakingEventId: stakingEventId }),
|
||||
});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const startStake = async (inventoryItemId: number, wellId: number) => {
|
||||
const response = await fetch(`/api/user/${userId}/stakes/start`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
inventoryItemId: inventoryItemId,
|
||||
wellId: wellId,
|
||||
}),
|
||||
});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Which object is it?
|
||||
const sellResource = async (obj: any) => {
|
||||
const response = await fetch(`/api/user/${userId}/bank-account`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
obj: obj,
|
||||
}),
|
||||
});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const buyStoreItem = async (itemId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/user/${userId}/inventory-items`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
itemId: itemId,
|
||||
}),
|
||||
});
|
||||
updatedStakingSources && setStakingSources(updatedStakingSources);
|
||||
};
|
||||
if (stakingSources && stakingSources?.length > 0) {
|
||||
updateItemsOnStakingSources();
|
||||
}
|
||||
}, [inventoryItems]);
|
||||
|
||||
const handleAddItem = (
|
||||
inventoryItem: IInventoryItem,
|
||||
stakingSource: IStakingSource
|
||||
) => {
|
||||
const newStakingSources = stakingSources?.map((source) => {
|
||||
if (source.id === stakingSource.id) {
|
||||
return { ...source, inventoryItem: inventoryItem };
|
||||
} else {
|
||||
return source;
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
setError(new Error(error));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (newStakingSources) {
|
||||
setStakingSources(newStakingSources);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.status == 200) {
|
||||
// Return success message
|
||||
console.log(data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setError(error);
|
||||
} else {
|
||||
setError(new Error("An unknown error occurred."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleBuy = (storeItem: IStoreItem) => {
|
||||
const hasItem = inventoryItems?.some((item) => {
|
||||
item.storeItem.id === storeItem.id;
|
||||
});
|
||||
const upgradeInventoryItem = async (itemId: number) => {
|
||||
try {
|
||||
const response = await fetch(`/api/user/${userId}/inventory-items`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
itemId: itemId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (hasItem) return;
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
setError(new Error(error));
|
||||
return;
|
||||
}
|
||||
|
||||
const getNewIndex = () => {
|
||||
if (!inventoryItems) return 0;
|
||||
return inventoryItems.length;
|
||||
};
|
||||
const data = await response.json();
|
||||
|
||||
const newInventoryItem = {
|
||||
id: getNewIndex(),
|
||||
stakingSource: null,
|
||||
storeItem: storeItem,
|
||||
currentTierIndex: 0,
|
||||
};
|
||||
if (response.status == 200) {
|
||||
// Return success message
|
||||
console.log(data.message);
|
||||
}
|
||||
|
||||
if (inventoryItems && inventoryItems !== undefined) {
|
||||
setInventoryItems([...inventoryItems, newInventoryItem]);
|
||||
if (response.status == 400) {
|
||||
// return error message
|
||||
setError(data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
setError(error);
|
||||
} else {
|
||||
setError(new Error("An unknown error occurred."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const claimResource = (
|
||||
stakingSource: IStakingSource,
|
||||
claimedResource: IClaimableResource
|
||||
): boolean => {
|
||||
const bankAccount = bankAccounts.find(
|
||||
(obj) => obj["resourceType"]["name"] === claimedResource.resourceType.name
|
||||
);
|
||||
|
||||
if (!bankAccount) return false;
|
||||
decrementResourceWell(stakingSource, claimedResource);
|
||||
incrementBalance(bankAccount, claimedResource.balance);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const decrementResourceWell = (
|
||||
stakingSource: IStakingSource,
|
||||
claimedResource: IClaimableResource
|
||||
) => {
|
||||
const updatedResourceWells = stakingSource.resourceWells.map((well) => {
|
||||
if (well.resourceType.name === claimedResource.resourceType.name) {
|
||||
return { ...well, supply: well.supply - claimedResource.balance };
|
||||
} else {
|
||||
return well;
|
||||
}
|
||||
const createStakingSource = async () => {
|
||||
const response = await fetch(`/api/user/${userId}/staking-sources`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
const updatedStakingSources = stakingSources?.map((source) => {
|
||||
if (source.id === stakingSource.id) {
|
||||
return { ...source, resourceWells: updatedResourceWells };
|
||||
} else {
|
||||
return source;
|
||||
}
|
||||
});
|
||||
|
||||
updatedStakingSources && setStakingSources(updatedStakingSources);
|
||||
};
|
||||
|
||||
const decrementBalance = () => {};
|
||||
|
||||
const incrementBalance = (bankAccount: IBankAccount, amount: number) => {
|
||||
const updatedBankAccounts = bankAccounts.map((account) => {
|
||||
if (account.id === bankAccount.id) {
|
||||
return { ...account, balance: account.balance + amount };
|
||||
} else {
|
||||
return account;
|
||||
}
|
||||
});
|
||||
setBankAccounts(updatedBankAccounts);
|
||||
};
|
||||
|
||||
const getStoreItemConfiguration = () => {};
|
||||
|
||||
const handleIncrementTier = (inventoryItem: IInventoryItem) => {
|
||||
console.log("Incrementing Tier");
|
||||
// Check user has balance
|
||||
// Decrement user balance
|
||||
|
||||
if (inventoryItem.currentTierIndex === 4) return;
|
||||
|
||||
const updatedInventoryItems = inventoryItems?.map((item) => {
|
||||
if (item.id === inventoryItem.id) {
|
||||
return { ...item, currentTierIndex: item.currentTierIndex + 1 };
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
|
||||
if (updatedInventoryItems !== undefined)
|
||||
setInventoryItems(updatedInventoryItems);
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
const handleSetLightBox = () => {
|
||||
@ -206,8 +252,11 @@ export default function Home() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
<ErrorPopover message={error.message} onClose={handleCloseError} />
|
||||
)}
|
||||
<BankAccountsView
|
||||
bankAccounts={bankAccounts}
|
||||
bankAccount={bankAccount}
|
||||
setLightBoxIsActive={handleSetLightBox}
|
||||
/>
|
||||
{lightBoxIsActive && (
|
||||
@ -220,18 +269,17 @@ export default function Home() {
|
||||
<StakingSourcesView
|
||||
stakingSources={stakingSources}
|
||||
inventoryItems={inventoryItems}
|
||||
handleAddItem={handleAddItem}
|
||||
claimResource={claimResource}
|
||||
claimStake={claimStake}
|
||||
/>
|
||||
<InventoryItemView
|
||||
stakingSources={stakingSources}
|
||||
inventoryItems={inventoryItems}
|
||||
handleIncrementTier={handleIncrementTier}
|
||||
upgradeInventoryItem={upgradeInventoryItem}
|
||||
/>
|
||||
<StoreItemView
|
||||
storeItems={storeItems}
|
||||
inventoryItems={inventoryItems}
|
||||
handleBuy={handleBuy}
|
||||
buyStoreItem={buyStoreItem}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
39
src/pages/api/drop.ts
Normal file
39
src/pages/api/drop.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const db = await dbConnection;
|
||||
const users = "DROP TABLE users";
|
||||
await db.all(users);
|
||||
const resource = "DROP TABLE resource";
|
||||
await db.all(resource);
|
||||
const staking_source = "DROP TABLE staking_source";
|
||||
await db.all(staking_source);
|
||||
const resource_well = "DROP TABLE resource_well";
|
||||
await db.all(resource_well);
|
||||
const staking_event = "DROP TABLE staking_event";
|
||||
await db.all(staking_event);
|
||||
const claim_event = "DROP TABLE claim_event";
|
||||
await db.all(claim_event);
|
||||
const upgrade_event = "DROP TABLE upgrade_event";
|
||||
await db.all(upgrade_event);
|
||||
const store_item = "DROP TABLE store_item";
|
||||
await db.all(store_item);
|
||||
const inventory_item = "DROP TABLE inventory_item";
|
||||
await db.all(inventory_item);
|
||||
const bank_account = "DROP TABLE bank_account";
|
||||
await db.all(bank_account);
|
||||
const resource_account = "DROP TABLE resource_account";
|
||||
await db.all(resource_account);
|
||||
|
||||
res.status(200).json("Tables dropped");
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
}
|
27
src/pages/api/seed.ts
Normal file
27
src/pages/api/seed.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import * as fs from 'fs';
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const db = await dbConnection;
|
||||
|
||||
// Read SQL queries from file
|
||||
const sql1 = fs.readFileSync('sql/tables.sql', 'utf-8');
|
||||
|
||||
// Execute the SQL queries
|
||||
await db.exec(sql1);
|
||||
|
||||
const sql2 = fs.readFileSync('sql/data.sql', 'utf-8');
|
||||
await db.exec(sql2);
|
||||
|
||||
res.status(200).json("Database seeded");
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error.message);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IStoreItem } from "typings";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
// query all store items
|
||||
const storeItems: IStoreItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Eclipse Drill",
|
||||
description:
|
||||
"A compact and lightweight drill designed for use in tight and narrow mining tunnels.",
|
||||
price: 225,
|
||||
timeToClaim: 3,
|
||||
tiers: [
|
||||
{
|
||||
tier: 500,
|
||||
price: 50,
|
||||
},
|
||||
{
|
||||
tier: 600,
|
||||
price: 75,
|
||||
},
|
||||
{
|
||||
tier: 700,
|
||||
price: 100,
|
||||
},
|
||||
{
|
||||
tier: 800,
|
||||
price: 150,
|
||||
},
|
||||
{
|
||||
tier: 900,
|
||||
price: 200,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Moon Saw 2000",
|
||||
description:
|
||||
"A compact and lightweight drill designed for use in tight and narrow mining tunnels.",
|
||||
price: 100,
|
||||
timeToClaim: 3,
|
||||
tiers: [
|
||||
{
|
||||
tier: 500,
|
||||
price: 50,
|
||||
},
|
||||
{
|
||||
tier: 550,
|
||||
price: 75,
|
||||
},
|
||||
{
|
||||
tier: 600,
|
||||
price: 100,
|
||||
},
|
||||
{
|
||||
tier: 650,
|
||||
price: 150,
|
||||
},
|
||||
{
|
||||
tier: 700,
|
||||
price: 200,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// if no store items send empty array
|
||||
if (!storeItems) return res.status(200).json({ message: [] });
|
||||
|
||||
// if store items found send store items
|
||||
return res.status(200).json({ message: storeItems });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Unexpexted server error" });
|
||||
}
|
||||
}
|
27
src/pages/api/user/[userId]/add-balance/[amount].ts
Normal file
27
src/pages/api/user/[userId]/add-balance/[amount].ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
// http://127.0.0.1:3000/api/user/1/add-balance/100
|
||||
// Remember to change to POST
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const { userId, amount } = req.query;
|
||||
const db = await dbConnection;
|
||||
const sql = `
|
||||
UPDATE bank_account SET balance = balance + ?
|
||||
WHERE user_id = ?`;
|
||||
const result = await db.run(sql, [amount, userId])
|
||||
if (result.changes == 0) {
|
||||
return res.status(400).json({error: `User ${userId} was not found`});
|
||||
} else {
|
||||
return res.status(200).json({message: result});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return res.status(500).json(error);
|
||||
}
|
||||
}
|
48
src/pages/api/user/[userId]/bank-account.ts
Normal file
48
src/pages/api/user/[userId]/bank-account.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const { userId } = req.query;
|
||||
const db = await dbConnection;
|
||||
const userSql = `SELECT * FROM users WHERE id = ?`;
|
||||
const user = await db.all(userSql, [userId]);
|
||||
if (user.length === 0) return res.status(404).json("User not found");
|
||||
|
||||
const resourceAccountsSql = `
|
||||
SELECT id,resname as resourceType,balance
|
||||
FROM resource_account
|
||||
WHERE user_id = ?`;
|
||||
// need resourceName or at least a way to map together with gameConfiq
|
||||
const resourceAccounts = await db.all(resourceAccountsSql, [userId]);
|
||||
|
||||
const bankAccountSql = `SELECT * FROM bank_account WHERE user_id = ?`;
|
||||
const bankAccount = await db.all(bankAccountSql, [userId]);
|
||||
|
||||
const bankAccountObj = {
|
||||
id: bankAccount[0].id,
|
||||
primaryBalance: bankAccount[0].balance,
|
||||
resourceAccounts: resourceAccounts,
|
||||
};
|
||||
|
||||
return res.status(200).json(bankAccountObj);
|
||||
}
|
||||
|
||||
if (req.method === "PUT") {
|
||||
// sell resource
|
||||
// payload userId, key:resourceName/value:amount
|
||||
const { userId } = req.query;
|
||||
const { obj } = req.body;
|
||||
// make sure they have each resource they say they have
|
||||
// calculate conversion rates
|
||||
// increment balance
|
||||
// decrement resources
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IBankAccount, IResourceType } from "typings";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const { userId } = req.query;
|
||||
|
||||
// query db for user ID
|
||||
const user = true;
|
||||
// if user not found send error
|
||||
if (!user) return res.status(404).json({ message: "User not found" });
|
||||
|
||||
// if user found query all bank accounts attached to this user
|
||||
const DBresourceTypes: IResourceType[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Moonstone",
|
||||
fontColorClass: "text-teal-400",
|
||||
bgColorClass: "from-teal-800 to-teal-900",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Lunarite",
|
||||
fontColorClass: "text-cyan-400",
|
||||
bgColorClass: "from-cyan-800 to-cyan-900",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Selenite",
|
||||
fontColorClass: "text-purple-300",
|
||||
bgColorClass: "from-purple-800 to-purple-900",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Heliogem",
|
||||
fontColorClass: "text-rose-300",
|
||||
bgColorClass: "from-rose-800 to-rose-900",
|
||||
},
|
||||
];
|
||||
const bankAccounts: IBankAccount[] = [
|
||||
{
|
||||
id: 1,
|
||||
resourceType: DBresourceTypes[0],
|
||||
balance: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
resourceType: DBresourceTypes[1],
|
||||
balance: 0,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
resourceType: DBresourceTypes[2],
|
||||
balance: 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
resourceType: DBresourceTypes[3],
|
||||
balance: 0,
|
||||
},
|
||||
];
|
||||
|
||||
// if bank accounts not found send empty array
|
||||
if (!bankAccounts) return res.status(200).json({ message: [] });
|
||||
|
||||
// if bank accounts found send bank accounts
|
||||
return res.status(200).json({ message: bankAccounts });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Unexpexted server error" });
|
||||
}
|
||||
}
|
@ -1,26 +1,91 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IInventoryItem } from "typings";
|
||||
import { gameConfig } from "@/utils/gameLogic";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
// Get all owned items
|
||||
|
||||
const { userId } = req.query;
|
||||
const db = await dbConnection;
|
||||
const inventorySql = `
|
||||
SELECT id, store_item_id as storeItemId, tier as currentTierIndex
|
||||
FROM inventory_item WHERE user_id = ?`;
|
||||
const inventoryItems = await db.all(inventorySql, [userId]);
|
||||
return res.status(200).json(inventoryItems);
|
||||
|
||||
// query db for user ID
|
||||
const user = true;
|
||||
// if user not found send error
|
||||
if (!user) return res.status(404).json({ message: "User not found" });
|
||||
} else if (req.method === "POST") {
|
||||
// Buy a new item
|
||||
const { userId } = req.query;
|
||||
const { itemId } = req.body;
|
||||
const db = await dbConnection;
|
||||
|
||||
// if user found query all inventory items attached to this user
|
||||
const inventoryItems: IInventoryItem[] = [];
|
||||
const storeItem = gameConfig.store.find((item) => item.id == itemId);
|
||||
|
||||
// if inventory items not found send empty array
|
||||
if (!inventoryItems) return res.status(200).json({ message: [] });
|
||||
if (storeItem == undefined) {
|
||||
return res.status(400).json({ error: "Item does not exist" });
|
||||
}
|
||||
const itemPrice = storeItem.price;
|
||||
// TODO: Split the try catch to report already owned item error
|
||||
try {
|
||||
await db.run("BEGIN");
|
||||
await db.run(`UPDATE bank_account SET balance = balance - ?
|
||||
WHERE user_id = ?`, [itemPrice, userId]);
|
||||
await db.run(`INSERT INTO inventory_item (user_id, store_item_id)
|
||||
VALUES (?, ?)`, [userId, itemId]);
|
||||
await db.run("COMMIT");
|
||||
} catch (error) {
|
||||
await db.exec("ROLLBACK");
|
||||
return res.status(400).json({ error: "Either Insufficient funds or item is already owned" });
|
||||
}
|
||||
return res.status(200).json({ message: "Item purchased successfully" });
|
||||
|
||||
// if inventory items found send inventory items
|
||||
return res.status(200).json({ message: inventoryItems });
|
||||
} else if (req.method === "PUT") {
|
||||
// Upgrade an existing item
|
||||
const { userId } = req.query;
|
||||
const { itemId } = req.body;
|
||||
const db = await dbConnection;
|
||||
|
||||
const invSql = "SELECT id,tier,store_item_id FROM inventory_item WHERE id = ? AND user_id = ?";
|
||||
|
||||
const invItem = await db.get(invSql, [itemId, userId]);
|
||||
|
||||
const storeItem = gameConfig.store.find((item) => item.id == invItem.store_item_id);
|
||||
if (storeItem == undefined) {
|
||||
return res.status(400).json({ error: "Item does not exist" });
|
||||
}
|
||||
|
||||
const tier = invItem.tier;
|
||||
if (tier == undefined) {
|
||||
return res.status(400).json({ error: "Item does not exist" });
|
||||
}
|
||||
if (tier >= storeItem.upgrades.length) {
|
||||
return res.status(400).json({ error: "Max upgrade reached" });
|
||||
}
|
||||
const upgradePrice = storeItem.upgrades[tier].price;
|
||||
try {
|
||||
await db.run("BEGIN");
|
||||
await db.run(`UPDATE bank_account SET balance = balance - ?
|
||||
WHERE user_id = ?`, [upgradePrice, userId]);
|
||||
await db.run(`UPDATE inventory_item SET tier = tier + 1
|
||||
WHERE user_id = ? AND store_item_id = ?;`, [userId, itemId]);
|
||||
await db.run("INSERT INTO upgrade_event(inventory_item_id) VALUES ( ? )", [invItem.store_item_id]);
|
||||
await db.run("COMMIT");
|
||||
} catch (error) {
|
||||
await db.exec("ROLLBACK");
|
||||
return res.status(400).json({ error: "Insufficient funds" });
|
||||
}
|
||||
return res.status(200).json({ message: "Successfully upgraded item" });
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Unexpexted server error" });
|
||||
if(error instanceof Error){
|
||||
res.status(500).json(error.message);
|
||||
}else {
|
||||
res.status(500).json("Unknow Error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
src/pages/api/user/[userId]/stakes/claim.ts
Normal file
60
src/pages/api/user/[userId]/stakes/claim.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IInventoryItem } from "typings";
|
||||
import {
|
||||
calculateRemainingTime,
|
||||
} from "../../../../../utils/helpers";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "POST") {
|
||||
const { userId } = req.query;
|
||||
const { stakingEventId } = req.body;
|
||||
|
||||
const db = await dbConnection;
|
||||
try {
|
||||
const stake = await db.get("SELECT * FROM staking_event WHERE id = ? AND user_id = ?",
|
||||
[stakingEventId, userId]);
|
||||
|
||||
if (stake == undefined) {
|
||||
return res.status(400).json({error: "Could not find stake"});
|
||||
}
|
||||
|
||||
const claim = await db.get("SELECT id FROM claim_event WHERE staking_event_id = ?",
|
||||
[stakingEventId])
|
||||
if (claim != undefined) {
|
||||
return res.status(400).json({error: "Stake already claimed"});
|
||||
}
|
||||
|
||||
const timeRemaining = calculateRemainingTime(stake.created_at, stake.duration_in_mins);
|
||||
if (timeRemaining > 0) {
|
||||
return res.status(400).json({error: "Staking period has not ended"});
|
||||
}
|
||||
const well = await db.get("SELECT id,resname,supply FROM resource_well WHERE id = ?",
|
||||
[stake.well_id])
|
||||
|
||||
const claimAmount = stake.stake_amount > well.supply ? well.supply : stake.stake_amount;
|
||||
|
||||
await db.run("BEGIN");
|
||||
await db.run("INSERT INTO claim_event(staking_event_id, claim_amount) VALUES (?, ?)",
|
||||
[stake.id, claimAmount])
|
||||
console.log(well.id);
|
||||
const r1 = await db.run(`UPDATE resource_well SET supply = supply - ? WHERE id = ?`,
|
||||
[claimAmount, well.id]);
|
||||
console.log(stake.user_id, well.resname);
|
||||
const r2 = await db.run(`UPDATE resource_account SET balance = balance + ?
|
||||
WHERE user_id = ? AND resname = ?`, [claimAmount, stake.user_id, well.resname]);
|
||||
await db.run("COMMIT");
|
||||
} catch (error) {
|
||||
await db.exec("ROLLBACK");
|
||||
return res.status(400).json({ error: error.message});
|
||||
}
|
||||
return res.status(200).json({result: "Successfully claimed stake"});
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
}
|
28
src/pages/api/user/[userId]/stakes/index.ts
Normal file
28
src/pages/api/user/[userId]/stakes/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IInventoryItem } from "typings";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const { userId } = req.query;
|
||||
const db = await dbConnection;
|
||||
const inventorySql = `
|
||||
SELECT staking_event.id, staking_source.id as sourceId, resname as resourceType,
|
||||
inventory_item_id, duration_in_mins, stake_amount, staking_event.created_at,
|
||||
CASE WHEN claim_event.staking_event_id IS NULL THEN 1 ELSE 0 END AS unclaimed
|
||||
FROM staking_event
|
||||
INNER JOIN resource_well ON well_id = resource_well.id
|
||||
INNER JOIN staking_source ON source_id = staking_source.id
|
||||
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
|
||||
WHERE staking_event.user_id = ?`;
|
||||
const inventoryItems = await db.all(inventorySql, [userId]);
|
||||
return res.status(200).json(inventoryItems);
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
}
|
79
src/pages/api/user/[userId]/stakes/start.ts
Normal file
79
src/pages/api/user/[userId]/stakes/start.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import {
|
||||
calculateRemainingTime,
|
||||
} from "../../../../../utils/helpers";
|
||||
|
||||
import { gameConfig } from "@/utils/gameLogic";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "POST") {
|
||||
const { userId } = req.query;
|
||||
const {
|
||||
inventoryItemId,
|
||||
wellId,
|
||||
} = req.body;
|
||||
|
||||
const db = await dbConnection;
|
||||
try {
|
||||
const inventorySql = `
|
||||
SELECT inventory_item.id, tier, store_item_id, staking_event.id as stakeId,
|
||||
staking_event.created_at as stakeTime, duration_in_mins, stake_amount
|
||||
FROM inventory_item
|
||||
LEFT JOIN staking_event ON inventory_item_id = inventory_item.id
|
||||
WHERE inventory_item.store_item_id = ? AND inventory_item.user_id = ?
|
||||
ORDER BY staking_event.created_at;
|
||||
`;
|
||||
|
||||
const invItem = await db.get(inventorySql, [inventoryItemId, userId]);
|
||||
const well = await db.get(`
|
||||
SELECT resource_well.id, resname, source_id, supply as wellId FROM resource_well
|
||||
INNER JOIN staking_source ON source_id = staking_source.id
|
||||
WHERE resource_well.id = ? AND user_id = ?;
|
||||
`, [wellId, userId]);
|
||||
|
||||
if (well == undefined) {
|
||||
return res.status(400).json({error: `Well ${wellId} not found`});
|
||||
}
|
||||
|
||||
const item = gameConfig.store.find((i) => i.id == invItem.store_item_id);
|
||||
if (invItem == undefined || well == undefined || item == undefined) {
|
||||
return res.status(400).json({ error: "A resource was not found" });
|
||||
}
|
||||
|
||||
if (invItem.stakeId != undefined) {
|
||||
const timeRemaining = calculateRemainingTime(invItem.stakeTime, invItem.duration_in_mins);
|
||||
console.log(timeRemaining);
|
||||
if (timeRemaining > 0) {
|
||||
return res.status(400).json({error: `Item is still in use ${timeRemaining}`});
|
||||
}
|
||||
}
|
||||
|
||||
const boost = invItem.tier > 0 ? item.upgrades[invItem.tier - 1].claimBoost : 0;
|
||||
const totalClaim = item.claimAmount + boost;
|
||||
console.log(userId + " " + well.id + " " + invItem.id)
|
||||
await db.run(`INSERT INTO staking_event(user_id, well_id, inventory_item_id, duration_in_mins, stake_amount)
|
||||
VALUES (?,?,?,?,?)`,
|
||||
[userId, well.id, invItem.id, item.completionTimeInMins, totalClaim]);
|
||||
} catch (error) {
|
||||
if(error instanceof Error){
|
||||
return res.status(400).json({ error: error.message});
|
||||
}else {
|
||||
return res.status(400).json({ error: "Unknown Error"});
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({result: "Successfully started a stake"});
|
||||
}
|
||||
} catch (error) {
|
||||
if(error instanceof Error){
|
||||
res.status(500).json(error.message);
|
||||
}else {
|
||||
res.status(500).json({ error: "Unknown Error"});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +1,93 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { IStakingSource } from "typings";
|
||||
import { gameConfig } from "@/utils/gameLogic";
|
||||
import {
|
||||
generateRandomBase64String,
|
||||
} from "../../../../utils/helpers";
|
||||
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
const { userId } = req.query;
|
||||
const db = await dbConnection;
|
||||
const sourcesSql = `
|
||||
SELECT id, name, description FROM staking_source
|
||||
WHERE staking_source.user_id = ?`;
|
||||
const stakingSources = await db.all(sourcesSql, [userId]);
|
||||
for (const source of stakingSources) {
|
||||
const wellsSql = `
|
||||
SELECT id, resname as resourceType, supply FROM resource_well
|
||||
WHERE source_id = ?`;
|
||||
const resourceWells = await db.all(wellsSql, [source.id]);
|
||||
source.resourceWells = resourceWells;
|
||||
const stakesSql = `
|
||||
SELECT staking_event.id, resname as resourceType, source_id as stakingSourceId,
|
||||
inventory_item_id, duration_in_mins, stake_amount,
|
||||
staking_event.created_at as startTime
|
||||
FROM staking_event
|
||||
INNER JOIN resource_well ON well_id = resource_well.id
|
||||
INNER JOIN staking_source ON source_id = staking_source.id
|
||||
LEFT JOIN claim_event ON staking_event.id = claim_event.staking_event_id
|
||||
WHERE staking_source.id = ? AND claim_event.staking_event_id IS NULL;`;
|
||||
const activeStakes = await db.all(stakesSql, [source.id]);
|
||||
source.activeStakes = activeStakes;
|
||||
}
|
||||
const typedStakingSources: IStakingSource[] = stakingSources.map(source => ({
|
||||
...source,
|
||||
// Emil: The variable names are messed up so i'm renaming them
|
||||
activeStakes: source.activeStakes.map((stake) => ({
|
||||
...stake,
|
||||
inventoryItemId: stake.inventory_item_id,
|
||||
durationInMins: stake.duration_in_mins,
|
||||
stakeAmount: stake.stake_amount
|
||||
}))
|
||||
}))
|
||||
|
||||
// query db for user ID
|
||||
const user = true;
|
||||
// if user not found send error
|
||||
if (!user) return res.status(404).json({ message: "User not found" });
|
||||
return res.status(200).json(typedStakingSources);
|
||||
}
|
||||
if (req.method === "POST") {
|
||||
const { userId } = req.query;
|
||||
const db = await dbConnection;
|
||||
|
||||
// if user found query all staking sources attached to this user
|
||||
const stakingSources: IStakingSource[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Selene's Eye",
|
||||
description:
|
||||
"Selene's Eye is a large and mysterious moon, named for its distinctive appearance - a bright, glowing eye that seems to stare out from the void of space",
|
||||
resourceWells:[
|
||||
{
|
||||
id: 1,
|
||||
resourceType: {
|
||||
id: 1,
|
||||
name: "Moonstone",
|
||||
fontColorClass: "text-teal-400",
|
||||
bgColorClass: "from-teal-800 to-teal-900",
|
||||
},
|
||||
supply: 10000,
|
||||
}
|
||||
],
|
||||
inventoryItem: null,
|
||||
},
|
||||
];
|
||||
try {
|
||||
const randomName = "Moon 1";
|
||||
const randomDescription = "This is a moon orbiting Selene's planet";
|
||||
const randomKey = "0x" + generateRandomBase64String(16);
|
||||
const resMax = gameConfig.moons.resourceMaxStartAmount;
|
||||
const resMin = gameConfig.moons.resourceMinStartAmount;
|
||||
const moonPrice = gameConfig.moons.price;
|
||||
|
||||
// if staking sources not found send empty array
|
||||
if (!stakingSources) return res.status(200).json({ message: [] });
|
||||
|
||||
// if staking sources found send staking sources
|
||||
return res.status(200).json({ message: stakingSources });
|
||||
await db.run("BEGIN");
|
||||
await db.run(`UPDATE bank_account SET balance = balance - ?
|
||||
WHERE user_id = ?`, [moonPrice, userId]);
|
||||
const result = await db.run(`INSERT INTO staking_source(user_id, name, description, address)
|
||||
VALUES (?, ?, ?, ?)`, [userId, randomName, randomDescription, randomKey]);
|
||||
const sourceId = result.lastID;
|
||||
for (const resname of gameConfig.resources) {
|
||||
if (Math.random() < gameConfig.moons.resourceChance) {
|
||||
const randomNumber = Math.random();
|
||||
const range = resMax - resMin + 1;
|
||||
const initSupply = Math.floor(Math.random() * range + resMin);
|
||||
await db.run(`INSERT INTO resource_well (source_id, resname, supply)
|
||||
VALUES (?, ?, ?)`, [sourceId, resname, initSupply]);
|
||||
}
|
||||
}
|
||||
await db.run("COMMIT");
|
||||
return res.status(200).json({"stakingSourceId": sourceId});
|
||||
} catch (error) {
|
||||
await db.exec("ROLLBACK");
|
||||
if (error.message.includes("CHECK constraint failed")) {
|
||||
return res.status(400).json({ "error": "Insuficcient funds" });
|
||||
} else {
|
||||
return res.status(400).json({ "error": error.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Unexpexted server error" });
|
||||
res.status(500).json(error.message);
|
||||
}
|
||||
}
|
||||
|
26
src/pages/api/user/login.ts
Normal file
26
src/pages/api/user/login.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { dbConnection } from "db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
try {
|
||||
if (req.method === "POST") {
|
||||
const { wallet } = req.body;
|
||||
// const db = await dbConnection;
|
||||
// const user = await db.get("SELECT id FROM users WHERE wallet = ?", [wallet]);
|
||||
|
||||
// Comment me out
|
||||
return res.status(200).json({ userId: 1});
|
||||
|
||||
/* if (user != undefined) {
|
||||
return res.status(200).json({ userId: user.id});
|
||||
} else {
|
||||
return res.status(404).json({ message: "User not found" });
|
||||
} */
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json(error);
|
||||
}
|
||||
}
|
3
src/utils/gameLogic.ts
Normal file
3
src/utils/gameLogic.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { IGameConfig } from "typings";
|
||||
|
||||
export const gameConfig = process.env.gameConfig as unknown as IGameConfig;
|
@ -1,3 +1,6 @@
|
||||
import { TimeDuration } from "typings";
|
||||
import crypto from 'crypto';
|
||||
|
||||
export function getObjectFromArray<T>(array: T[], key: keyof T, value: any): T | undefined {
|
||||
return array.find(obj => obj[key] === value);
|
||||
}
|
||||
@ -9,5 +12,59 @@ export function pushElementToArray<T>(array: T[], element: T): T[] {
|
||||
}
|
||||
|
||||
export function sumValues<T extends Record<K, number>, K extends keyof T>(array: T[], key: K): number {
|
||||
return array.reduce((acc, obj) => acc + obj[key], 0);
|
||||
}
|
||||
return array.reduce((acc, obj) => acc + obj[key], 0);
|
||||
}
|
||||
|
||||
interface IMapping {
|
||||
[key: string]: any
|
||||
}
|
||||
export function resourceToBg(resourceName: string): string {
|
||||
|
||||
const mapping: IMapping = {
|
||||
"Sollux": "from-teal-800 to-teal-900",
|
||||
"Shadowstone": "from-cyan-800 to-cyan-900",
|
||||
"Azurium": "from-purple-800 to-purple-900",
|
||||
"Novafor": "from-rose-800 to-rose-900",
|
||||
"Nebulance": "from-rose-800 to-rose-900"
|
||||
}
|
||||
|
||||
return mapping[resourceName]
|
||||
}
|
||||
|
||||
export function resourceToFc(resourceName: string): string {
|
||||
|
||||
const mapping: IMapping = {
|
||||
"Sollux": "text-teal-400",
|
||||
"Shadowstone": "text-cyan-400",
|
||||
"Azurium": "text-purple-300",
|
||||
"Novafor": "text-rose-300",
|
||||
"Nebulance": "text-rose-300"
|
||||
}
|
||||
|
||||
return mapping[resourceName]
|
||||
}
|
||||
|
||||
export const calculateRemainingTime = (startTime: string, durationInMins: number) => {
|
||||
const start = new Date(startTime);
|
||||
const end = new Date(start.getTime() + durationInMins * 60000);
|
||||
const remaining = end.getTime() - Date.now();
|
||||
return remaining;
|
||||
};
|
||||
|
||||
export const prettifyTime = (remainingTime: number) => {
|
||||
const hours = Math.floor((remainingTime / (1000 * 60 * 60)) % 24);
|
||||
const minutes = Math.floor((remainingTime / (1000 * 60)) % 60);
|
||||
const seconds = Math.floor((remainingTime / 1000) % 60);
|
||||
return { hours, minutes, seconds };
|
||||
}
|
||||
|
||||
export const generateRandomBase64String = (length: number) => {
|
||||
// Create a random byte array of the desired length.
|
||||
const byteArray = new Uint8Array(length);
|
||||
crypto.getRandomValues(byteArray);
|
||||
|
||||
// Convert the byte array to a base64 string.
|
||||
const base64String = btoa(String.fromCharCode(...byteArray));
|
||||
|
||||
return base64String;
|
||||
}
|
||||
|
52
test-endpoints.rest
Normal file
52
test-endpoints.rest
Normal file
@ -0,0 +1,52 @@
|
||||
:headers = <<
|
||||
Content-Type: application/json
|
||||
#
|
||||
|
||||
# Get Inventory Items
|
||||
GET http://localhost:3000/api/seed
|
||||
:headers
|
||||
|
||||
# Get Inventory Items
|
||||
POST http://localhost:3000/api/user/login
|
||||
:headers
|
||||
{ "wallet" : "Wallet12345678" }
|
||||
|
||||
# Get Inventory Items
|
||||
GET http://localhost:3000/api/user/1/bank-account
|
||||
:headers
|
||||
|
||||
# Get Inventory Items
|
||||
GET http://localhost:3000/api/user/1/staking-sources
|
||||
:headers
|
||||
|
||||
# Get Inventory Items
|
||||
POST http://localhost:3000/api/user/1/staking-sources
|
||||
:headers
|
||||
|
||||
# Get Inventory Items
|
||||
GET http://localhost:3000/api/user/1/inventory-items
|
||||
:headers
|
||||
|
||||
# Buy a new Item
|
||||
POST http://localhost:3000/api/user/1/inventory-items/
|
||||
:headers
|
||||
{ "itemId" : "item3" }
|
||||
|
||||
# Upgrade an owned item
|
||||
PUT http://localhost:3000/api/user/1/inventory-items/
|
||||
:headers
|
||||
{ "itemId" : "item1" }
|
||||
|
||||
# Get stakes
|
||||
GET http://localhost:3000/api/user/1/stakes/
|
||||
:headers
|
||||
|
||||
# Start a stake
|
||||
POST http://localhost:3000/api/user/1/stakes/start
|
||||
:headers
|
||||
{ "inventoryItemId": "item1", "wellId": 7 }
|
||||
|
||||
# Claim a stake
|
||||
POST http://localhost:3000/api/user/1/stakes/claim
|
||||
:headers
|
||||
{ "stakingEventId" : 4 }
|
91
typings.d.ts
vendored
91
typings.d.ts
vendored
@ -11,16 +11,29 @@ export interface TimeDuration {
|
||||
|
||||
export interface IResourceWell {
|
||||
id: number;
|
||||
resourceType: IResourceType;
|
||||
resourceType: string;
|
||||
supply: number;
|
||||
}
|
||||
|
||||
export interface IStake {
|
||||
id: number;
|
||||
resourceType: string;
|
||||
stakingSourceId: number;
|
||||
startTime: string;
|
||||
inventoryItemId: number;
|
||||
stakeAmount: number;
|
||||
durationInMins: number;
|
||||
unclaimed: boolean;
|
||||
claimable: boolean | undefined
|
||||
remainingTime: number | undefined
|
||||
}
|
||||
|
||||
export interface IStakingSource {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
resourceWells: IResourceWell[];
|
||||
inventoryItem: IInventoryItem | null;
|
||||
activeStakes: IStake[];
|
||||
}
|
||||
|
||||
export interface IInventoryItem {
|
||||
@ -30,26 +43,35 @@ export interface IInventoryItem {
|
||||
}
|
||||
|
||||
export interface IStoreItem {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
price: number;
|
||||
timeToClaim: number;
|
||||
tiers: {
|
||||
completionTimeInMins: number;
|
||||
claimAmount: number;
|
||||
upgrades: {
|
||||
tier: number;
|
||||
price: number;
|
||||
claimBoost: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface IResourceAccount {
|
||||
id: number;
|
||||
resourceType: string;
|
||||
balance: number;
|
||||
}
|
||||
|
||||
export interface IBankAccount {
|
||||
id: number;
|
||||
resourceType: IResourceType;
|
||||
balance: number;
|
||||
primaryBalance: number;
|
||||
resourceAccounts: IResourceAccount[];
|
||||
}
|
||||
|
||||
export interface IClaimableResource {
|
||||
resourceType: IResourceType;
|
||||
balance: number;
|
||||
export interface IGameConfig {
|
||||
resources: string[];
|
||||
[key: string]: any;
|
||||
store: IStoreItem[];
|
||||
}
|
||||
|
||||
export interface IConversionPair {
|
||||
@ -57,3 +79,52 @@ export interface IConversionPair {
|
||||
resourceAmount: number;
|
||||
moneyAmount: number
|
||||
}
|
||||
|
||||
// Phantom
|
||||
export interface PhantomProvider {
|
||||
publicKey: PublicKey | null;
|
||||
isConnected: boolean | null;
|
||||
signAndSendTransaction: (
|
||||
transaction: Transaction,
|
||||
opts?: SendOptions
|
||||
) => Promise<{ signature: string; publicKey: PublicKey }>;
|
||||
signTransaction: (transaction: Transaction) => Promise<Transaction>;
|
||||
signAllTransactions: (transactions: Transaction[]) => Promise<Transaction[]>;
|
||||
signMessage: (
|
||||
message: Uint8Array | string,
|
||||
display?: DisplayEncoding
|
||||
) => Promise<any>;
|
||||
connect: (opts?: Partial<ConnectOpts>) => Promise<{ publicKey: PublicKey }>;
|
||||
disconnect: () => Promise<void>;
|
||||
on: (event: PhantomEvent, handler: (args: any) => void) => void;
|
||||
request: (method: PhantomRequestMethod, params: any) => Promise<unknown>;
|
||||
}
|
||||
|
||||
type DisplayEncoding = "utf8" | "hex";
|
||||
|
||||
interface ConnectOpts {
|
||||
onlyIfTrusted: boolean;
|
||||
}
|
||||
|
||||
type PhantomEvent = "connect" | "disconnect" | "accountChanged";
|
||||
|
||||
type PhantomRequestMethod =
|
||||
| "connect"
|
||||
| "disconnect"
|
||||
| "signAndSendTransaction"
|
||||
| "signTransaction"
|
||||
| "signAllTransactions"
|
||||
| "signMessage";
|
||||
|
||||
|
||||
// Generic stuff
|
||||
|
||||
export interface IOption {
|
||||
value: string,
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface ISelectDropdownProps {
|
||||
options: IOption[];
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user