From 801532759df1866cb43f4d6afe64d15e3bd93809 Mon Sep 17 00:00:00 2001 From: Emil Andreas Nielsen <72539155+freakspace@users.noreply.github.com> Date: Fri, 24 Feb 2023 13:37:07 +0700 Subject: [PATCH] Feature/resource yields (#3) * Add handleSetUpgrade function and other related stuff * Fix select drill to upgrade * Small refactor - apply drill on upgrade * Refactor types - WIP * Add handleBuy function, sort storeItems by owned * Add tiers to storeItems * Show equipment on moon, add radiobutton to select * Modify types * Cleanup some code and comments etc. * Add claim function to increment user balance and decrement well * Add API to get data * Set inventoryItem and activeTier in state * Update stakingSource whenever inventoryItems are updated * Disable upgrade button when mining is active * Add initial implementation of lightbox --------- Co-authored-by: Nico Li --- src/app/Components/BankAccount.tsx | 33 ++ src/app/Components/BankAccountsView.tsx | 33 ++ src/app/Components/InventoryItem.tsx | 51 +++ src/app/Components/InventoryItemView.tsx | 33 ++ src/app/Components/LightBox.tsx | 12 + src/app/Components/MiningItem.tsx | 52 --- src/app/Components/MiningView.tsx | 16 - src/app/Components/MoonItem.tsx | 57 --- src/app/Components/MoonsView.tsx | 76 ---- src/app/Components/ResourceItem.tsx | 51 --- src/app/Components/ResourceView.tsx | 34 -- src/app/Components/StakingSource.tsx | 187 +++++++++ src/app/Components/StakingSourcesView.tsx | 35 ++ src/app/Components/StoreItem.tsx | 60 +++ src/app/Components/StoreItemView.tsx | 41 ++ src/app/Components/UpgradeItem.tsx | 57 --- src/app/Components/UpgradeView.tsx | 16 - src/app/page.tsx | 375 +++++++++++++++--- src/pages/api/hello.ts | 12 - src/pages/api/store/items.ts | 80 ++++ src/pages/api/user/[userId]/bank-accounts.ts | 73 ++++ .../api/user/[userId]/inventory-items.ts | 32 ++ .../api/user/[userId]/staking-sources.ts | 35 ++ src/utils/helpers.ts | 8 +- tsconfig.json | 25 +- typings.d.ts | 73 ++-- 26 files changed, 1102 insertions(+), 455 deletions(-) create mode 100644 src/app/Components/BankAccount.tsx create mode 100644 src/app/Components/BankAccountsView.tsx create mode 100644 src/app/Components/InventoryItem.tsx create mode 100644 src/app/Components/InventoryItemView.tsx create mode 100644 src/app/Components/LightBox.tsx delete mode 100644 src/app/Components/MiningItem.tsx delete mode 100644 src/app/Components/MiningView.tsx delete mode 100644 src/app/Components/MoonItem.tsx delete mode 100644 src/app/Components/MoonsView.tsx delete mode 100644 src/app/Components/ResourceItem.tsx delete mode 100644 src/app/Components/ResourceView.tsx create mode 100644 src/app/Components/StakingSource.tsx create mode 100644 src/app/Components/StakingSourcesView.tsx create mode 100644 src/app/Components/StoreItem.tsx create mode 100644 src/app/Components/StoreItemView.tsx delete mode 100644 src/app/Components/UpgradeItem.tsx delete mode 100644 src/app/Components/UpgradeView.tsx delete mode 100644 src/pages/api/hello.ts create mode 100644 src/pages/api/store/items.ts create mode 100644 src/pages/api/user/[userId]/bank-accounts.ts create mode 100644 src/pages/api/user/[userId]/inventory-items.ts create mode 100644 src/pages/api/user/[userId]/staking-sources.ts diff --git a/src/app/Components/BankAccount.tsx b/src/app/Components/BankAccount.tsx new file mode 100644 index 0000000..19248f4 --- /dev/null +++ b/src/app/Components/BankAccount.tsx @@ -0,0 +1,33 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { IBankAccount } from "typings"; + +const BankAccount = (props: { bankAccount: IBankAccount }) => { + return ( +
+
+ + {props.bankAccount.resourceType.name} + +

+ {props.bankAccount.balance.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })}{" "} + kg +

+
+
+ ); +}; + +export default BankAccount; diff --git a/src/app/Components/BankAccountsView.tsx b/src/app/Components/BankAccountsView.tsx new file mode 100644 index 0000000..642832d --- /dev/null +++ b/src/app/Components/BankAccountsView.tsx @@ -0,0 +1,33 @@ +"use client"; +import React from "react"; +import { IBankAccount } from "typings"; +import BankAccount from "./BankAccount"; + +const BankAccountsView = (props: { + bankAccounts: IBankAccount[]; + setLightBoxIsActive: () => void; +}) => { + return ( +
+
+
+
+ Moonbucks +

$5,342.23

+ +
+
+ {props.bankAccounts.map((bankAccount, id) => { + return ; + })} +
+
+ ); +}; + +export default BankAccountsView; diff --git a/src/app/Components/InventoryItem.tsx b/src/app/Components/InventoryItem.tsx new file mode 100644 index 0000000..5652334 --- /dev/null +++ b/src/app/Components/InventoryItem.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { IInventoryItem, IStoreItem } from "typings"; +import CardLayout from "../Layouts/CardLayout"; + +const InventoryItem = (props: { + inventoryItem: IInventoryItem; + inUse: boolean | undefined; + handleIncrementTier: (inventoryItem: IInventoryItem) => void; +}) => { + const getCurrentTier = (index: number) => { + return props.inventoryItem.storeItem.tiers[index]; + }; + + return ( + +

+ {props.inventoryItem.storeItem.name}{" "} + + {props.inventoryItem.currentTierIndex + 1} + +

+

{props.inventoryItem.storeItem.description}

+
+
+

Yield

+
    + {getCurrentTier(props.inventoryItem.currentTierIndex).tier} +
+
+
+
+ {props.inUse ? ( + + ) : ( + + )} +
+
+
+
+ ); +}; + +export default InventoryItem; diff --git a/src/app/Components/InventoryItemView.tsx b/src/app/Components/InventoryItemView.tsx new file mode 100644 index 0000000..3425852 --- /dev/null +++ b/src/app/Components/InventoryItemView.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { IInventoryItem, IStakingSource } from "typings"; +import InventoryItem from "./InventoryItem"; + +const InventoryItemView = (props: { + stakingSources: IStakingSource[] | null; + inventoryItems: IInventoryItem[] | null | undefined; + handleIncrementTier: (inventoryItem: IInventoryItem) => void; +}) => { + const inUse = (inventoryItemId: number) => { + return props.stakingSources?.some((source) => { + if (!source.inventoryItem) return false; + return source.inventoryItem.id == inventoryItemId; + }); + }; + + return ( +
+

Your Inventory

+ {props.inventoryItems && + props.inventoryItems.map((inventoryItem, id) => ( + + ))} +
+ ); +}; + +export default InventoryItemView; diff --git a/src/app/Components/LightBox.tsx b/src/app/Components/LightBox.tsx new file mode 100644 index 0000000..14b87ca --- /dev/null +++ b/src/app/Components/LightBox.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { IBankAccount } from "typings"; + +const LightBox = (props: { bankAccounts: IBankAccount[] }) => { + return ( +
+

Sell Your Resources

+
+ ); +}; + +export default LightBox; diff --git a/src/app/Components/MiningItem.tsx b/src/app/Components/MiningItem.tsx deleted file mode 100644 index 7064085..0000000 --- a/src/app/Components/MiningItem.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from "react"; -import { Drill } from "typings"; -import CardLayout from "../Layouts/CardLayout"; - -const MiningItem = (props: { drill: Drill }) => { - return ( - -

{props.drill.name}

-

{props.drill.description}

-
-
-

Base Yield

-
    - {props.drill.yield.map( - (x, id) => - x.resource && ( -
  • - {x.baseYield}{" "} - - {x.resource.name} - -
  • - ) - )} -
-

Upgrades

-
    -
  • - SkyMiner MK-III -
  • -
-
-
-

Status

-
    -
  • - Active Mining on Moon 1 -
  • -
-

Moon

- -
-
-
- ); -}; - -export default MiningItem; diff --git a/src/app/Components/MiningView.tsx b/src/app/Components/MiningView.tsx deleted file mode 100644 index e533ad8..0000000 --- a/src/app/Components/MiningView.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import { Drill } from "typings"; -import MiningItem from "./MiningItem"; - -const MiningView = (props: { drills: Drill[] }) => { - return ( -
-

Drills

- {props.drills.map((drill, id) => ( - - ))} -
- ); -}; - -export default MiningView; diff --git a/src/app/Components/MoonItem.tsx b/src/app/Components/MoonItem.tsx deleted file mode 100644 index 539bcfd..0000000 --- a/src/app/Components/MoonItem.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; -import { Moon } from "typings"; -import CardLayout from "../Layouts/CardLayout"; - -const MoonItem = (props: { moon: Moon }) => { - return ( - -

{props.moon.name}

-

{props.moon.description}

-
-
-

Mineral Composition

-
    - {props.moon.resources.map((resource, id) => ( -
  • - {resource.name}{" "} - 80% -
  • - ))} -
-
-
-

Status

-
    -
  • - - - - - - Operational -
  • -
-

Drill

-
    -
  • - Eclipse Drill -
  • -
-
-
-
- ); -}; - -export default MoonItem; diff --git a/src/app/Components/MoonsView.tsx b/src/app/Components/MoonsView.tsx deleted file mode 100644 index b9f9037..0000000 --- a/src/app/Components/MoonsView.tsx +++ /dev/null @@ -1,76 +0,0 @@ -"use client"; -import React, { useEffect, useState } from "react"; -import { Moon } from "typings"; -import MoonItem from "./MoonItem"; - -const MoonsView = (props: { moons: Moon[] }) => { - const [isMining, setIsMining] = useState(false); - const [miningTime, setMiningTime] = useState(3); - const [farmedResources, setFarmedResources] = useState([]); - - const resources = ["Moonstone", "Lunarite", "Selenite", "Heliogem"]; - - useEffect(() => { - if (isMining) { - const intervalId = setInterval(() => { - setMiningTime((prev) => prev - 1); - }, 1000); - - if (miningTime === 0) { - setIsMining(false); - setFarmedResources((prev) => [ - ...prev, - resources[Math.floor(Math.random() * (resources.length - 1 - 1))], - ]); - clearInterval(intervalId); - setMiningTime(3); - } - - return () => { - clearInterval(intervalId); - }; - } - }, [isMining, miningTime]); - - return ( -
-

Moons

- {props.moons.map((moon, id) => ( - - ))} -
- - {isMining &&
Mining time: {miningTime}
} -
-
Here you will see your mined resources
- {farmedResources.length <= 0 ? ( -
Start mining to get resources
- ) : ( - <> - {farmedResources.map((farmedResource, index) => ( -
- {farmedResource} - -
- ))} - - )} -
- ); -}; - -export default MoonsView; diff --git a/src/app/Components/ResourceItem.tsx b/src/app/Components/ResourceItem.tsx deleted file mode 100644 index acb39af..0000000 --- a/src/app/Components/ResourceItem.tsx +++ /dev/null @@ -1,51 +0,0 @@ -"use client"; -import React, { useEffect, useState } from "react"; -import { IResourceCardProps } from "typings"; - -const ResourceItem = (props: { resourceCardProps: IResourceCardProps }) => { - const [totalYield, settotalYield] = useState(100); - const [isMining, setIsMining] = useState(props.resourceCardProps.isMining); - - useEffect(() => { - if (isMining) { - const intervalId = setInterval(() => { - settotalYield((prev) => prev + props.resourceCardProps.yieldPerSecond); - }, 1000); - - return () => { - clearInterval(intervalId); - }; - } - }, [totalYield]); - - return ( -
-
- - {props.resourceCardProps.resource.name} - -

- {totalYield.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })}{" "} - kg -

- - {props.resourceCardProps.yieldPerSecond * 3600} / hour - -
-
- ); -}; - -export default ResourceItem; diff --git a/src/app/Components/ResourceView.tsx b/src/app/Components/ResourceView.tsx deleted file mode 100644 index b9fcbd9..0000000 --- a/src/app/Components/ResourceView.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; -import React from "react"; -import { Resource } from "typings"; -import ResourceItem from "./ResourceItem"; - -const ResourceView = (props: { resources: Resource[] }) => { - return ( -
-
-
-
- Moonbucks -

$5,342.23

- -
-
- {props.resources.map((resource, id) => { - const resourceCardProps = { - resource: resource, - isMining: true, - yieldPerSecond: 0.15, - }; - return ( - - ); - })} -
-
- ); -}; - -export default ResourceView; diff --git a/src/app/Components/StakingSource.tsx b/src/app/Components/StakingSource.tsx new file mode 100644 index 0000000..b75d891 --- /dev/null +++ b/src/app/Components/StakingSource.tsx @@ -0,0 +1,187 @@ +import React, { useState, useEffect } from "react"; +import { IInventoryItem, IStakingSource, IClaimableResource } from "typings"; +import CardLayout from "../Layouts/CardLayout"; + +const StakingSource = (props: { + stakingSource: IStakingSource; + inventoryItems: IInventoryItem[] | null | undefined; + handleAddItem: ( + inventoryItem: IInventoryItem, + stakingSource: IStakingSource + ) => void; + claimResource: ( + stakingSource: IStakingSource, + claimedResource: IClaimableResource + ) => boolean; +}) => { + const [isMining, setIsMining] = useState(false); + const [miningTime, setMiningTime] = useState(0); + const [claimableResource, setClaimableResource] = + useState(null); + const [activeInventoryItem, setActiveInventoryItem] = + useState(null); + const [activeTier, setActiveTier] = useState(0); + + 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 ( + + ); + } + } + + if (isMining) + return ( + + ); + + if (claimableResource) { + return ( + + ); + } + }; + + return ( + +

{props.stakingSource.name}

+

{props.stakingSource.description}

+
+
+

Resources

+
    + {props.stakingSource.resourceWells.map((resourceWell, id) => ( +
  • + {resourceWell.resourceType.name}{" "} + + {resourceWell.supply} + +
  • + ))} +
+
+
+

Status

+
    +
  • + + + + + + Operational +
  • +
+

Equipment

+ {!isMining + ? props.inventoryItems && + props.inventoryItems.map((item, id) => ( +
+ + props.handleAddItem(item, props.stakingSource) + } + /> + +
+ )) + : props.stakingSource.inventoryItem && ( +

{props.stakingSource.inventoryItem.storeItem.name}

+ )} +
+
+ {renderButton()} +
+ ); +}; + +export default StakingSource; diff --git a/src/app/Components/StakingSourcesView.tsx b/src/app/Components/StakingSourcesView.tsx new file mode 100644 index 0000000..1aa191e --- /dev/null +++ b/src/app/Components/StakingSourcesView.tsx @@ -0,0 +1,35 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { IInventoryItem, IStakingSource, IClaimableResource } 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; +}) => { + return ( +
+

Your Moons

+ {props.stakingSources && + props.stakingSources.map((stakingSource, id) => ( + + ))} +
+ ); +}; + +export default StakingSourcesView; diff --git a/src/app/Components/StoreItem.tsx b/src/app/Components/StoreItem.tsx new file mode 100644 index 0000000..dc81392 --- /dev/null +++ b/src/app/Components/StoreItem.tsx @@ -0,0 +1,60 @@ +"use client"; +import React, { useState } from "react"; +import { IStoreItem, IInventoryItem } from "typings"; +import CommonCardLayout from "../Layouts/CommonCardLayout"; + +const StoreItem = (props: { + storeItem: IStoreItem; + handleBuy: (storeItem: IStoreItem) => void; + owned: boolean | undefined; +}) => { + return ( + +
+
+ {props.owned ? ( + Owned + ) : ( + "" + )} +

{props.storeItem.name}

+

{props.storeItem.description}

+

$ {props.storeItem.price}

+

Tier Upgrades

+ + + + + + {props.storeItem.tiers.map((tier) => ( + + + + + ))} +
TierPrice
+ {tier.tier} + + ${tier.price} +
+
+
+ {props.owned ? ( + + ) : ( + + )} +
+
+
+ ); +}; + +export default StoreItem; diff --git a/src/app/Components/StoreItemView.tsx b/src/app/Components/StoreItemView.tsx new file mode 100644 index 0000000..26a1ee0 --- /dev/null +++ b/src/app/Components/StoreItemView.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { IInventoryItem, IStoreItem } from "typings"; +import StoreItem from "./StoreItem"; + +const StoreItemView = (props: { + storeItems: IStoreItem[] | null; + inventoryItems: IInventoryItem[] | null | undefined; + handleBuy: (storeItem: IStoreItem) => void; +}) => { + const isOwned = (storeItemId: number) => { + return props.inventoryItems?.some( + (item) => item.storeItem.id == storeItemId + ); + }; + + const storeItemsToRender = props.storeItems?.map((storeItem) => { + return { + ...storeItem, + owned: isOwned(storeItem.id), + }; + }); + + return ( +
+

Store

+ {storeItemsToRender && + storeItemsToRender + .sort((a, b) => (a.owned === b.owned ? 0 : a.owned ? 1 : -1)) + .map((storeItem, id) => ( + + ))} +
+ ); +}; + +export default StoreItemView; diff --git a/src/app/Components/UpgradeItem.tsx b/src/app/Components/UpgradeItem.tsx deleted file mode 100644 index a262c88..0000000 --- a/src/app/Components/UpgradeItem.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; -import { Upgrade } from "typings"; -import CardLayout from "../Layouts/CardLayout"; -import RareCardLayout from "../Layouts/RareCardLayout"; -import EpicCardLayout from "../Layouts/EpicCardLayout"; -import PrestineCardLayout from "../Layouts/PrestineCardLayout"; -import CommonCardLayout from "../Layouts/CommonCardLayout"; - -const UpgradeItem = (props: { upgrade: Upgrade }) => { - return ( - -
-
- {props.upgrade.owned && ( - Owned - )} -

{props.upgrade.name}

-

{props.upgrade.description}

- {!props.upgrade.owned && ( -

${props.upgrade.price}

- )} -

Modifies

-
    - {props.upgrade.modifiers.map((modifier, id) => ( -
  • - {modifier.yield}{" "} - - {modifier.resource && modifier.resource.name} - -
  • - ))} -
-
- {!props.upgrade.owned ? ( -
- -
- ) : ( -
-

Drill

- -
- )} -
-
- ); -}; - -export default UpgradeItem; diff --git a/src/app/Components/UpgradeView.tsx b/src/app/Components/UpgradeView.tsx deleted file mode 100644 index 815aac8..0000000 --- a/src/app/Components/UpgradeView.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import { Upgrade } from "typings"; -import UpgradeItem from "./UpgradeItem"; - -const UpgradeView = (props: { upgrades: Upgrade[] }) => { - return ( -
-

Upgrades

- {props.upgrades.map((upgrade, id) => ( - - ))} -
- ); -}; - -export default UpgradeView; diff --git a/src/app/page.tsx b/src/app/page.tsx index ed02308..83ec836 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,97 +1,382 @@ -import MiningView from "./Components/MiningView"; -import MoonsView from "./Components/MoonsView"; -import ResourceView from "./Components/ResourceView"; -import UpgradeView from "./Components/UpgradeView"; -import { Resource, Upgrade, Drill, Moon } from "typings"; +"use client"; +import { useState, useEffect } from "react"; -import getObjectFromArray from "@/utils/helpers"; +import InventoryItemView from "./Components/InventoryItemView"; +import StakingSourcesView from "./Components/StakingSourcesView"; +import BankAccountsView from "./Components/BankAccountsView"; +import StoreItemView from "./Components/StoreItemView"; +import LightBox from "./Components/LightBox"; +import { + IResourceWell, + IResourceType, + IStoreItem, + IInventoryItem, + IStakingSource, + IBankAccount, + IClaimableResource, +} from "typings"; -const resources: Resource[] = [ +import { getObjectFromArray } from "../utils/helpers"; + +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 upgrades: Upgrade[] = [ +const DBresourceWells: IResourceWell[] = [ { - name: "SkyMiner MK-III", - description: - "A cutting-edge mining robot equipped with advanced sensors, capable of identifying and extracting even the rarest minerals on the moon's surface.", - price: 500, - modifiers: [ - { - resource: getObjectFromArray(resources, "name", "Moonstone"), - yield: 1, - }, - ], - owned: false, - }, - { - name: "Astro-Excavator 5000", - description: - "A state-of-the-art excavation machine, capable of digging deeper and faster than any other equipment on the market.", - price: 250, - modifiers: [ - { - resource: getObjectFromArray(resources, "name", "Selenite"), - yield: 1, - }, - ], - owned: true, + id: 1, + resourceType: DBresourceTypes[0], + supply: 10000, }, ]; -const drills: Drill[] = [ +const DBstoreItems: IStoreItem[] = [ { + id: 1, name: "Eclipse Drill", description: "A compact and lightweight drill designed for use in tight and narrow mining tunnels.", - price: 1500, - yield: [ + price: 225, + timeToClaim: 3, + tiers: [ { - resource: getObjectFromArray(resources, "name", "Lunarite"), - baseYield: 250, + 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, }, ], - upgrades: null, }, ]; -const moons: Moon[] = [ +const DBinventoryItems: IInventoryItem[] = []; + +const DBstakingSources: 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", - resources: resources, - drill: null, + resourceWells: DBresourceWells, + inventoryItem: null, + }, +]; + +const DBbankAccounts: 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, }, ]; export default function Home() { + const [inventoryItems, setInventoryItems] = useState< + IInventoryItem[] | null | undefined + >([]); + const [stakingSources, setStakingSources] = useState( + [] + ); + const [bankAccounts, setBankAccounts] = useState([]); + const [storeItems, setStoreItems] = useState([]); + const [lightBoxIsActive, setLightBoxIsActive] = useState(false); + + // Connect to DB here + useEffect(() => { + // get the user who is currently logged in + const loggedInUser = 1; + + const fetchInventoryItems = async () => { + // old + setInventoryItems(DBinventoryItems); + // new + // const response = await fetch(`/api/user/${loggedInUser}/inventory-items`); + // const DBInventoryItems = await response.json(); + // setInventoryItems(DBInventoryItems.message); + }; + + const fetchStakingSources = async () => { + // old + setStakingSources(DBstakingSources); + // new + // const response = await fetch(`/api/user/${loggedInUser}/staking-sources`); + // const DBStakingSources = await response.json(); + // setStakingSources(DBStakingSources); + }; + + const fetchBankAccounts = async () => { + // old + setBankAccounts(DBbankAccounts); + // new + // const response = await fetch(`/api/user/${loggedInUser}/bank-accounts`); + // const DBBankAccounts = await response.json(); + // setStakingSources(DBBankAccounts.message); + }; + + const fetchStoreItems = async () => { + // old + setStoreItems(DBstoreItems); + // new + // 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 + ); + if (item) { + return { ...source, inventoryItem: item }; + } else { + return source; + } + }); + 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 (newStakingSources) { + setStakingSources(newStakingSources); + } + }; + + const handleBuy = (storeItem: IStoreItem) => { + const hasItem = inventoryItems?.some((item) => { + item.storeItem.id === storeItem.id; + }); + + if (hasItem) return; + + const getNewIndex = () => { + if (!inventoryItems) return 0; + return inventoryItems.length; + }; + + const newInventoryItem = { + id: getNewIndex(), + stakingSource: null, + storeItem: storeItem, + currentTierIndex: 0, + }; + + if (inventoryItems && inventoryItems !== undefined) { + setInventoryItems([...inventoryItems, newInventoryItem]); + } + }; + + const claimResource = ( + stakingSource: IStakingSource, + claimedResource: IClaimableResource + ): boolean => { + // Known bug: If a Inventory is already selected, and then upgraded, claiming will be from old tier + const bankAccount = getBankAccount(claimedResource.resourceType); + 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 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 getBankAccount = (resourceType: IResourceType) => { + return getObjectFromArray(bankAccounts, "resourceType", resourceType); + }; + + 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); + }; + + const handleSetLightBox = () => { + setLightBoxIsActive(!lightBoxIsActive); + }; + + const renderLightBox = () => { + if (lightBoxIsActive) return ; + }; + return ( <> - +
- - - + + +
); diff --git a/src/pages/api/hello.ts b/src/pages/api/hello.ts deleted file mode 100644 index 78a0eba..0000000 --- a/src/pages/api/hello.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -type Data = { - name: string; -}; - -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: "Moon Miners!" }); -} diff --git a/src/pages/api/store/items.ts b/src/pages/api/store/items.ts new file mode 100644 index 0000000..ac4f044 --- /dev/null +++ b/src/pages/api/store/items.ts @@ -0,0 +1,80 @@ +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" }); + } +} diff --git a/src/pages/api/user/[userId]/bank-accounts.ts b/src/pages/api/user/[userId]/bank-accounts.ts new file mode 100644 index 0000000..131a35e --- /dev/null +++ b/src/pages/api/user/[userId]/bank-accounts.ts @@ -0,0 +1,73 @@ +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" }); + } +} diff --git a/src/pages/api/user/[userId]/inventory-items.ts b/src/pages/api/user/[userId]/inventory-items.ts new file mode 100644 index 0000000..16d54ea --- /dev/null +++ b/src/pages/api/user/[userId]/inventory-items.ts @@ -0,0 +1,32 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { IInventoryItem } 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 inventory items attached to this user + const inventoryItems: IInventoryItem[] = [ + { + id: 123, + storeItem: "Example StoreItem", + currentTierIndex: 0, + }, + ]; + + // if inventory items not found send empty array + if (!inventoryItems) return res.status(200).json({ message: [] }); + + // if inventory items found send inventory items + return res.status(200).json({ message: inventoryItems }); + } + } catch (error) { + res.status(500).json({ message: "Unexpexted server error" }); + } +} diff --git a/src/pages/api/user/[userId]/staking-sources.ts b/src/pages/api/user/[userId]/staking-sources.ts new file mode 100644 index 0000000..82caad3 --- /dev/null +++ b/src/pages/api/user/[userId]/staking-sources.ts @@ -0,0 +1,35 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { IStakingSource } 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 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: "Example DBresourceWells", + inventoryItem: null, + }, + ]; + + // 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 }); + } + } catch (error) { + res.status(500).json({ message: "Unexpexted server error" }); + } +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 7de1cd2..f58d17b 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,3 +1,9 @@ -export default function getObjectFromArray(array: T[], key: keyof T, value: any): T | undefined { +export function getObjectFromArray(array: T[], key: keyof T, value: any): T | undefined { return array.find(obj => obj[key] === value); + } + +export function pushElementToArray(array: T[], element: T): T[] { + const newArray = [...array]; // create a new array + newArray.push(element); // add the new element to the array + return newArray; // return the new array } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 6a9c1a2..dc36fb0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,12 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, - "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": true, @@ -21,9 +24,19 @@ ], "baseUrl": ".", "paths": { - "@/*": ["./src/*"] - } + "@/*": [ + "./src/*" + ] + }, + "skipLibCheck": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/typings.d.ts b/typings.d.ts index c1ee1ba..e607270 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1,45 +1,54 @@ +export type User = { + id: number; + inventoryItems: IInventoryItem[]; +}; -export type Resource = { - name: string, - fontColorClass: string, - bgColorClass: string +export interface IResourceType { + id: number; + name: string; + fontColorClass: string; + bgColorClass: string; } -export interface IResourceCardProps { - resource: Resource, - isMining: boolean, - yieldPerSecond: number +export interface IResourceWell { + id: number; + resourceType: IResourceType; + supply: number; } -export type Modifier = { - resource: Resource | undefined, - yield: number +export interface IStakingSource { + id: number; + name: string; + description: string; + resourceWells: IResourceWell[]; + inventoryItem: IInventoryItem | null; } -export type Yield = { - resource: Resource | undefined, - baseYield: number +export interface IInventoryItem { + id: number; + storeItem: IStoreItem; + currentTierIndex: number; } -export type Moon = { - name: string, - description: string, - resources: Resource[] - drill: Drill | null +export interface IStoreItem { + id: number; + name: string; + description: string; + price: number; + timeToClaim: number; + tiers: { + tier: number; + price: number; + }[]; } -export type Drill = { - name: string, - description: string, - price: number, - yield: Yield[] - upgrades: Upgrade[] | null +export interface IBankAccount { + id: number; + resourceType: IResourceType; + balance: number; } -export type Upgrade = { - name: string, - description: string, - price: number, - modifiers: Modifier[] - owned: boolean -} \ No newline at end of file +export interface IClaimableResource { + resourceType: IResourceType; + balance: number; +}