Feature/homepage (#2)

* Basic UI for resources

* Style upgrades etc.

* Add template for drill

* Add moons and drill templates

* Add mining timer

* Delete comment

* Modify some styling to cards and resources

* Enhance styling on difference components

---------

Co-authored-by: Nico Li <nilindenau@gmail.com>
This commit is contained in:
Emil Andreas Nielsen 2023-02-12 12:40:35 +07:00 committed by GitHub
parent 988920afc0
commit 0c1f15464f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 559 additions and 4 deletions

View File

@ -0,0 +1,52 @@
import React from "react";
import { Drill } from "typings";
import CardLayout from "../Layouts/CardLayout";
const MiningItem = (props: { drill: Drill }) => {
return (
<CardLayout>
<h3 className="text-xl font-bold mb-2">{props.drill.name}</h3>
<p className="text-sm">{props.drill.description}</p>
<div className="flex gap-4">
<div className="flex-1">
<p className="font-bold mt-4">Base Yield</p>
<ul className="list-none">
{props.drill.yield.map(
(x, id) =>
x.resource && (
<li key={id}>
<span>{x.baseYield}</span>{" "}
<span className="text-teal-500 font-bold">
{x.resource.name}
</span>
</li>
)
)}
</ul>
<p className="font-bold mt-4">Upgrades</p>
<ul className="list-none">
<li>
<span className="">SkyMiner MK-III</span>
</li>
</ul>
</div>
<div className="flex-1">
<p className="font-bold mt-4">Status</p>
<ul className="list-none">
<li>
<span className="">Active Mining on Moon 1</span>
</li>
</ul>
<p className="font-bold mt-4">Moon</p>
<select className="block bg-white px-4 py-2 pr-8 rounded shadow text-black cursor-pointer">
<option>Moon 1</option>
<option>Moon 2</option>
<option>Moon 3</option>
</select>
</div>
</div>
</CardLayout>
);
};
export default MiningItem;

View File

@ -0,0 +1,16 @@
import React from "react";
import { Drill } from "typings";
import MiningItem from "./MiningItem";
const MiningView = (props: { drills: Drill[] }) => {
return (
<div className="bg-slate-800 text-white p-4 rounded-lg">
<h2 className="text-2xl font-bold mb-4">Drills</h2>
{props.drills.map((drill, id) => (
<MiningItem key={id} drill={drill} />
))}
</div>
);
};
export default MiningView;

View File

@ -0,0 +1,57 @@
import React from "react";
import { Moon } from "typings";
import CardLayout from "../Layouts/CardLayout";
const MoonItem = (props: { moon: Moon }) => {
return (
<CardLayout>
<h3 className="text-xl font-bold mb-2">{props.moon.name}</h3>
<p className="text-sm">{props.moon.description}</p>
<div className="flex gap-4">
<div className="flex-1">
<p className="font-bold mt-4">Mineral Composition</p>
<ul className="list-none">
{props.moon.resources.map((resource, id) => (
<li key={id}>
<span>{resource.name}</span>{" "}
<span className="text-teal-500 font-bold">80%</span>
</li>
))}
</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">Drill</p>
<ul className="list-none">
<li>
<span className="">Eclipse Drill</span>
</li>
</ul>
</div>
</div>
</CardLayout>
);
};
export default MoonItem;

View File

@ -0,0 +1,76 @@
"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<string[]>([]);
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 (
<div className="bg-slate-800 p-4 rounded-lg text-white">
<h2 className="text-2xl mb-4 font-bold">Moons</h2>
{props.moons.map((moon, id) => (
<MoonItem key={id} moon={moon} />
))}
<div className="flex mt-5">
<button
className="bg-slate-100 text-slate-900 p-1 rounded-lg"
onClick={() => setIsMining(true)}
>
Start mining
</button>
{isMining && <div className="p-1 ml-5">Mining time: {miningTime}</div>}
</div>
<div className="mt-5"> Here you will see your mined resources</div>
{farmedResources.length <= 0 ? (
<div>Start mining to get resources</div>
) : (
<>
{farmedResources.map((farmedResource, index) => (
<div className="my-2" key={index}>
{farmedResource}
<button
className="bg-slate-100 text-slate-900 p-1 mx-2 rounded-lg"
onClick={() =>
setFarmedResources((prev) =>
prev.filter((_, idx) => idx !== index)
)
}
>
Claim
</button>
</div>
))}
</>
)}
</div>
);
};
export default MoonsView;

View File

@ -0,0 +1,51 @@
"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 (
<div
className={
props.resourceCardProps.resource.bgColorClass +
" bg-gradient-to-br hover:bg-gradient-to-tr rounded-lg p-3"
}
>
<div className="text-white">
<span
className={
props.resourceCardProps.resource.fontColorClass + " font-bold"
}
>
{props.resourceCardProps.resource.name}
</span>
<h3 className="text-2xl font-bold">
{totalYield.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}{" "}
kg
</h3>
<span className="text-sm">
{props.resourceCardProps.yieldPerSecond * 3600} / hour
</span>
</div>
</div>
);
};
export default ResourceItem;

View File

@ -0,0 +1,34 @@
"use client";
import React from "react";
import { Resource } from "typings";
import ResourceItem from "./ResourceItem";
const ResourceView = (props: { resources: Resource[] }) => {
return (
<div className="p-4">
<div className="grid grid-cols-5 gap-8">
<div className="bg-green-800 rounded-lg p-3">
<div className="text-white">
<span className="text-green-400">Moonbucks</span>
<h3 className="text-2xl font-bold">$5,342.23</h3>
<button className="px-2 text-sm mt-1 rounded-lg font-bold bg-green-400 text-green-800">
Sell Resources
</button>
</div>
</div>
{props.resources.map((resource, id) => {
const resourceCardProps = {
resource: resource,
isMining: true,
yieldPerSecond: 0.15,
};
return (
<ResourceItem key={id} resourceCardProps={resourceCardProps} />
);
})}
</div>
</div>
);
};
export default ResourceView;

View File

@ -0,0 +1,57 @@
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 (
<CommonCardLayout>
<div className="flex gap-4">
<div className="flex-1">
{props.upgrade.owned && (
<span className="text-green-600 font-bold">Owned</span>
)}
<h3 className="text-xl font-bold mb-2">{props.upgrade.name}</h3>
<p className="text-sm mb-3">{props.upgrade.description}</p>
{!props.upgrade.owned && (
<p className="text-lg font-bold">${props.upgrade.price}</p>
)}
<p className="font-bold mt-3">Modifies</p>
<ul className="list-none">
{props.upgrade.modifiers.map((modifier, id) => (
<li key={id}>
<span>{modifier.yield}</span>{" "}
<span
className={modifier.resource?.fontColorClass + " font-bold"}
>
{modifier.resource && modifier.resource.name}
</span>
</li>
))}
</ul>
</div>
{!props.upgrade.owned ? (
<div className="flex items-center h-100">
<button className=" bg-slate-100 text-slate-900 px-4 py-2 rounded-lg font-bold w-28 text-center">
Buy
</button>
</div>
) : (
<div className="flex-none items-center h-100">
<p className="font-bold mt-4">Drill</p>
<select className="block bg-white px-4 py-2 pr-8 rounded shadow text-black cursor-pointer">
<option>Drill 1</option>
<option>Drill 2</option>
<option>Drill 3</option>
</select>
</div>
)}
</div>
</CommonCardLayout>
);
};
export default UpgradeItem;

View File

@ -0,0 +1,16 @@
import React from "react";
import { Upgrade } from "typings";
import UpgradeItem from "./UpgradeItem";
const UpgradeView = (props: { upgrades: Upgrade[] }) => {
return (
<div className="bg-slate-800 p-4 rounded-lg">
<h2 className="text-2xl font-bold mb-4 text-white">Upgrades</h2>
{props.upgrades.map((upgrade, id) => (
<UpgradeItem key={id} upgrade={upgrade} />
))}
</div>
);
};
export default UpgradeView;

View File

@ -0,0 +1,11 @@
export default function CardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-black/25 bg-slate-700 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100">
{children}
</div>
);
}

View File

@ -0,0 +1,13 @@
export default function CommonCardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-black/25 bg-gradient-to-br from-slate-700 via-slate-600 to-slate-800 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100">
{children}
</div>
);
}
// rounded-lg border border-black/25 bg-gradient-to-br from-slate-700 via-slate-600 to-slate-800 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100

View File

@ -0,0 +1,11 @@
export default function EpicCardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-black/25 bg-gradient-to-br from-slate-800 via-purple-900 to-slate-900 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100">
{children}
</div>
);
}

View File

@ -0,0 +1,11 @@
export default function PrestineCardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-black/25 bg-gradient-to-br from-yellow-700 via-yellow-600 to-yellow-800 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100">
{children}
</div>
);
}

View File

@ -0,0 +1,11 @@
export default function RareCardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-black/25 bg-gradient-to-br from-slate-700 via-slate-600 to-slate-800 shadow-xl shadow-black/5 p-4 mb-4 text-slate-100">
{children}
</div>
);
}

View File

@ -8,7 +8,7 @@ export default function RootLayout({
return (
<html lang="en">
<head />
<body>{children}</body>
<body className="bg-slate-900">{children}</body>
</html>
);
}

View File

@ -1,7 +1,98 @@
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";
import getObjectFromArray from "@/utils/helpers";
const resources: Resource[] = [
{
name: "Moonstone",
fontColorClass: "text-teal-400",
bgColorClass: "from-teal-800 to-teal-900",
},
{
name: "Lunarite",
fontColorClass: "text-cyan-400",
bgColorClass: "from-cyan-800 to-cyan-900",
},
{
name: "Selenite",
fontColorClass: "text-purple-300",
bgColorClass: "from-purple-800 to-purple-900",
},
{
name: "Heliogem",
fontColorClass: "text-rose-300",
bgColorClass: "from-rose-800 to-rose-900",
},
];
const upgrades: Upgrade[] = [
{
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,
},
];
const drills: Drill[] = [
{
name: "Eclipse Drill",
description:
"A compact and lightweight drill designed for use in tight and narrow mining tunnels.",
price: 1500,
yield: [
{
resource: getObjectFromArray(resources, "name", "Lunarite"),
baseYield: 250,
},
],
upgrades: null,
},
];
const moons: Moon[] = [
{
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,
},
];
export default function Home() {
return (
<div className="h-screen flex justify-center items-center">
<h1 className="text-red-500 text-9xl">To the moon!</h1>;
<>
<ResourceView resources={resources} />
<div className="grid grid-cols-3 gap-8 p-4">
<MoonsView moons={moons} />
<MiningView drills={drills} />
<UpgradeView upgrades={upgrades} />
</div>
</>
);
}

3
src/utils/helpers.ts Normal file
View File

@ -0,0 +1,3 @@
export default function getObjectFromArray<T>(array: T[], key: keyof T, value: any): T | undefined {
return array.find(obj => obj[key] === value);
}

45
typings.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
export type Resource = {
name: string,
fontColorClass: string,
bgColorClass: string
}
export interface IResourceCardProps {
resource: Resource,
isMining: boolean,
yieldPerSecond: number
}
export type Modifier = {
resource: Resource | undefined,
yield: number
}
export type Yield = {
resource: Resource | undefined,
baseYield: number
}
export type Moon = {
name: string,
description: string,
resources: Resource[]
drill: Drill | null
}
export type Drill = {
name: string,
description: string,
price: number,
yield: Yield[]
upgrades: Upgrade[] | null
}
export type Upgrade = {
name: string,
description: string,
price: number,
modifiers: Modifier[]
owned: boolean
}