Completed v1.0.0

This commit is contained in:
Ceferino Patino 2022-04-07 20:39:08 -05:00
commit 0af8b77295
18 changed files with 1902 additions and 281 deletions

1
.gitignore vendored
View file

@ -169,6 +169,7 @@ node_modules
psd
thumb
sketch
**/build
### VisualStudioCode ###
.vscode/*

View file

@ -1,70 +0,0 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

View file

@ -3,11 +3,18 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"@mui/icons-material": "^5.6.0",
"@mui/material": "^5.6.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"axios": "^0.26.1",
"lodash": "^4.17.21",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.0",
"web-vitals": "^2.1.4"
},

44
client/public/data.json Normal file
View file

@ -0,0 +1,44 @@
{
"Autopet Rules 2-Pack": {
"cost": 21000
},
"Bits Talisman": {
"cost": 15000
},
"Block Zapper": {
"cost": 5000
},
"Builder's Wand": {
"cost": 12000
},
"Colossal Experience Bottle": {
"cost": 1200
},
"God Potion": {
"cost": 1500
},
"Heat Core": {
"cost": 3000
},
"Hologram": {
"cost": 2000
},
"Hyper Catalyst Upgrade": {
"cost": 300
},
"Jumbo Backpack Upgrade": {
"cost": 4000
},
"Kat Flower": {
"cost": 500
},
"Kismet Feather": {
"cost": 1350
},
"Minion Storage X-pender": {
"cost": 1500
},
"Ultimate Carrot Candy Upgrade": {
"cost": 8000
}
}

View file

@ -1,23 +0,0 @@
import React, { createContext, useState } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
const [profile, setProfile] = useState({
uuid: '',
profileID: '',
});
const UserContext = createContext([profile, setProfile]);
return (
<UserContext.Provider>
<BrowserRouter>
<Routes>
<Route path=''></Route>
</Routes>
</BrowserRouter>
</UserContext.Provider>
);
}
export default App;

69
client/src/App.jsx Normal file
View file

@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Navbar from './components/Navbar';
import MainPage from './components/MainPage';
import BitsToCoin from './components/BitsToCoin';
import NPCUser from './components/NPCUser';
function ExternLink({ to }) {
window.location.replace(to);
return null;
}
function App() {
const [user, setUser] = useState({
username: '',
uuid: '',
profileID: '',
});
const MinionLink = () => (
<ExternLink
to={`https://hyminions.herokuapp.com/minions?name=${user.username}`}
/>
);
const ForgeLink = () => (
<ExternLink
to={`https://hyminions.herokuapp.com/forge?name=${user.username}`}
/>
);
const SlotLink = () => (
<ExternLink
to={`https://hyminions.herokuapp.com/minionscost?name=${user.username}`}
/>
);
const EventsLink = () => (
<ExternLink to={`https://hyminions.herokuapp.com/events`} />
);
const BazaarLink = () => <ExternLink to='https://bazaartracker.com/' />;
const AuctionLink = () => (
<ExternLink to='https://www.brandonfowler.me/skyblockah/' />
);
return (
<BrowserRouter>
<Routes>
<Route path='' element={<Navbar user={[user, setUser]} />}>
<Route index element={<MainPage user={user} />} />
<Route path='calculators'>
<Route path='bits-to-coin' element={<BitsToCoin />} />
<Route path='minion' element={<MinionLink />} />
<Route path='forge' element={<ForgeLink />} />
<Route path='slot' element={<SlotLink />} />
</Route>
<Route path='npc-user' element={<NPCUser />} />
<Route path='events' element={<EventsLink />} />
<Route path='auction-house' element={<AuctionLink />} />
<Route path='bazaar' element={<BazaarLink />} />
<Route path='*' element={<Navigate to='' />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;

View file

@ -0,0 +1,109 @@
import React, { useState, useEffect } from 'react';
import { groupBy } from 'lodash';
import {
Paper,
Accordion,
AccordionSummary,
AccordionDetails,
Divider,
Button,
Typography,
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { useTheme } from '@mui/material/styles';
import { getAuctions } from '../utils/getData';
function BitsToCoin() {
const theme = useTheme();
const [bitAuctions, setBitAuctions] = useState([]);
useEffect(() => getBitAuctions(), []);
async function getBitAuctions() {
const bits = await fetch('../data.json').then((response) =>
response.json()
);
const auctions = await getAuctions().then(async (auctions) => {
const bitsItems = auctions.filter((auction) =>
Object.keys(bits).includes(auction.item_name)
);
return groupBy(bitsItems, 'item_name');
});
const data = Object.keys(auctions).reduce((acc, key) => {
const prices = auctions[key]
.map(({ starting_bid }) => starting_bid)
.sort((a, b) => a - b);
const effectiveLength = prices.length > 10 ? 10 : prices.length;
const averagePrice = Math.round(
prices
.slice(0, effectiveLength)
.reduce((acc, price) => acc + price, 0) / effectiveLength
);
acc.push({
name: key,
lowestBid: prices[0],
count: prices.length,
bitsToCoin: Math.round(prices[0] / bits[key].cost),
averagePrice,
});
return acc;
}, []);
setBitAuctions(() => data.sort((a, b) => b.bitsToCoin - a.bitsToCoin));
}
return (
<Paper
sx={{
...theme.typography.body2,
padding: '2vh',
color: theme.palette.text.secondary,
flexGrow: 1,
height: '89vh',
margin: '2vh',
overflowY: 'auto',
}}
>
<Typography variant='h6' textAlign='center'>
Best Bits to Coin Items
</Typography>
<Divider sx={{ paddingBottom: '2vh' }}>
<Button onClick={getBitAuctions}>Refresh</Button>
</Divider>
{bitAuctions ? (
bitAuctions.map((item) => (
<Accordion key={item.name}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{item.name} ({item.bitsToCoin})
</AccordionSummary>
<AccordionDetails>
<Typography>
Lowest Bid: {item.lowestBid}
</Typography>
<Typography>
Average Price: {item.averagePrice}
</Typography>
<Typography>
Number of Auctions: {item.count}
</Typography>
</AccordionDetails>
</Accordion>
))
) : (
<Typography textAlign='center'>Loading...</Typography>
)}
</Paper>
);
}
export default BitsToCoin;

View file

@ -0,0 +1,39 @@
import React from 'react';
import { Paper, Typography, Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import UserDisplay from './UserDisplay';
function MainPage({ user }) {
const theme = useTheme();
return (
<Paper
sx={{
...theme.typography.body2,
padding: '2vh',
color: theme.palette.text.secondary,
flexGrow: 1,
height: '89vh',
margin: '2vh',
overflowY: 'auto',
}}
>
{user.profileID ? (
<UserDisplay user={user} />
) : (
<Box
display='flex'
justifyContent='center'
alignItems='center'
sx={{ flexGrow: 1, height: '85vh' }}
>
<Typography>Nothing to see here</Typography>
</Box>
)}
</Paper>
);
}
export default MainPage;

View file

@ -0,0 +1,91 @@
import React, { useState, useEffect } from 'react';
import {
Button,
FormGroup,
Paper,
TextField,
Typography,
Stack,
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { getItems } from '../utils/getData';
function NPCUser() {
const theme = useTheme();
const [search, setSearch] = useState('');
const [itemData, setItemData] = useState([]);
useEffect(() => handleSubmit(), []);
function handleSearchChange(e) {
setSearch(() => e.target.value);
}
async function handleSubmit(e) {
e?.preventDefault();
const items = await getItems();
const data = items
.filter((item) => item.name === search)
.map((item) => item.npc_sell_price);
setItemData(() => data[0]);
}
return (
<Paper
sx={{
...theme.typography.body2,
padding: '2vh',
color: theme.palette.text.secondary,
flexGrow: 1,
height: '89vh',
margin: '2vh',
overflowY: 'auto',
}}
>
<Typography textAlign='center'>NPC Price</Typography>
<form onSubmit={handleSubmit}>
<FormGroup>
<TextField
label='Item Name'
value={search}
onChange={handleSearchChange}
/>
<Button
type='submit'
color='inherit'
variant='outlined'
disableElevation
sx={{ padding: '0.5vh' }}
>
Submit
</Button>
</FormGroup>
</form>
<Stack
direction='row'
justifyContent='center'
alignItems='center'
spacing={2}
sx={{ height: '70vh' }}
>
{itemData ? (
<Typography textAlign='center'>
An {search} sells for {itemData} coins to an NPC
</Typography>
) : (
<Typography textAlign='center'>
This item cannot be sold to NPCs
</Typography>
)}
</Stack>
</Paper>
);
}
export default NPCUser;

View file

@ -0,0 +1,228 @@
import React, { useState } from 'react';
import { Link, Outlet } from 'react-router-dom';
import {
AppBar,
Box,
Button,
CssBaseline,
FormControl,
FormGroup,
InputLabel,
Menu,
MenuItem,
Select,
TextField,
Toolbar,
Typography,
useMediaQuery,
} from '@mui/material';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import axiosGet from '../utils/axiosGet';
import PageBase from './utils/PageBase';
import BaseLink from './utils/BaseLink';
function Navbar(props) {
const [user, setUser] = props.user;
const [username, setUsername] = useState('');
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const [profiles, setProfiles] = useState([]);
const [menu, setMenus] = useState({
anchorEl: null,
open: '',
});
const linkStyle = { textDecoration: 'none', color: 'inherit' };
const darkTheme = createTheme({
palette: {
mode: prefersDarkMode ? 'dark' : 'light',
},
breakpoints: {
values: {
xs: 0,
sm: 768,
md: 1024,
},
},
});
function handleProfileChange(e) {
setUser((initial) => ({ ...initial, [e.target.name]: e.target.value }));
}
function handleUsernameChange(e) {
setUsername(() => e.target.value);
}
function handleMenuOpen(e) {
setMenus(() => ({ anchorEl: e.target, open: e.target.name }));
}
function handleMenuClose() {
setMenus(() => ({ anchorEl: null, open: '' }));
}
async function handleSubmit(e) {
e.preventDefault();
await axiosGet(`https://api.ashcon.app/mojang/v2/user/${username}`)
.then(async (res) => {
setUser((initial) => ({
...initial,
uuid: res.data.uuid,
username,
}));
return await axiosGet(
`https://api.hypixel.net/skyblock/profiles`,
{
uuid: res.data.uuid,
key: process.env.REACT_APP_API_KEY,
}
);
})
.then((res) => {
setUser((initial) => ({ ...initial, profile: '' }));
setProfiles([]);
const data = res.data.profiles;
if (data) {
const profiles = res.data.profiles.map((profile) => {
const { profile_id, cute_name } = profile;
return { profileID: profile_id, cuteName: cute_name };
});
setProfiles(() => profiles);
}
});
}
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<AppBar position='static'>
<Toolbar>
<Typography
variant='h6'
component='div'
sx={{ margin: '0 2vh 0 0' }}
>
<Link to='/' style={linkStyle}>
Hypixel Helper
</Link>
</Typography>
<Button
onClick={handleMenuOpen}
name='calculators'
color='inherit'
>
Calculators
</Button>
<Button
onClick={handleMenuOpen}
name='misc'
color='inherit'
>
Misc
</Button>
<Menu
anchorEl={menu.anchorEl}
open={menu.open === 'calculators'}
onClose={handleMenuClose}
name='calculators'
>
<MenuItem>
<BaseLink to='calculators/bits-to-coin'>
Best Bits to Coin
</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='calculators/minion'>
Minion Profit
</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='calculators/forge'>
Best Forges
</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='calculators/slot'>
Optimal Minion Slot
</BaseLink>
</MenuItem>
</Menu>
<Menu
anchorEl={menu.anchorEl}
open={menu.open === 'misc'}
onClose={handleMenuClose}
name='misc'
>
<MenuItem>
<BaseLink to='auction-house'>
Auction Item Comparison
</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='events'>Events</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='bazaar'>Bazaar Trends</BaseLink>
</MenuItem>
<MenuItem>
<BaseLink to='npc-user'>NPC Price</BaseLink>
</MenuItem>
</Menu>
<Box sx={{ flexGrow: 1 }} />
{profiles.length !== 0 && (
<FormControl sx={{ width: '20vw' }}>
<InputLabel>Select Profile</InputLabel>
<Select
value={user.profileID}
onChange={handleProfileChange}
label='Profile'
name='profileID'
>
{profiles.map((profile, index) => (
<MenuItem
value={profile.profileID}
key={index}
>
{profile.cuteName}
</MenuItem>
))}
</Select>
</FormControl>
)}
<form onSubmit={handleSubmit}>
<FormGroup row>
<TextField
label='Username'
name='username'
value={username}
onChange={handleUsernameChange}
/>
<Button
type='submit'
color='inherit'
variant='outlined'
disableElevation
sx={{ padding: '0 3vh' }}
>
Submit
</Button>
</FormGroup>
</form>
</Toolbar>
</AppBar>
<PageBase>
<Outlet />
</PageBase>
</ThemeProvider>
);
}
export default Navbar;

View file

@ -0,0 +1,120 @@
import React, { useState, useEffect } from 'react';
import {
Grid,
Typography,
Divider,
Button,
Stack,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { getAuctions, getUserForgeData } from '../utils/getData';
function UserDisplay({ user }) {
const [forges, setForges] = useState([]);
const [auctions, setAuctions] = useState([]);
useEffect(() => {
forgeData();
auctionData();
}, []);
async function forgeData() {
const data = await getUserForgeData(user.username, user.profileID);
setForges(() =>
data?.processes
? data.processes
: 'You have no forges currently running'
);
}
async function auctionData() {
const data = await getAuctions();
const auctions = data.filter(
(auction) => auction.profile_id === user.profileID
);
setAuctions(() =>
auctions.length > 0
? auctions
: 'You have no auctions currently running'
);
}
return (
<Grid container spacing={2} sx={{ flexGrow: 1, height: '85vh' }}>
<Grid item xs={6}>
<Typography variant='h6' textAlign='center'>
Forges
</Typography>
<Divider>
<Button onClick={forgeData}>Refresh</Button>
</Divider>
<Stack>
{Array.isArray(forges) ? (
forges.map((forge) => (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
Slot {forge.slot}: {forge.name}
</AccordionSummary>
<AccordionDetails>
<Typography>
Complete: {forge.timeFinishedText}
</Typography>
<Typography>
Exact Time:{' '}
{Date.parse(forge.timeFinished)}
</Typography>
</AccordionDetails>
</Accordion>
))
) : (
<Typography textAlign='center'>{forges}</Typography>
)}
</Stack>
</Grid>
<Grid item xs={6}>
<Typography variant='h6' textAlign='center'>
Auctions
</Typography>
<Divider>
<Button onClick={auctionData}>Refresh</Button>
</Divider>
<Stack>
{Array.isArray(auctions) ? (
auctions.map((auction) => (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>
{auction.item_name} ({auction.tier})
</AccordionSummary>
<AccordionDetails>
<Typography>
Starting Bid: {auction.starting_bid}
</Typography>
<Typography>
Highest Bid:{' '}
{auction.highest_bid_amount}
</Typography>
</AccordionDetails>
</Accordion>
))
) : (
<Typography textAlign='center'>{auctions}</Typography>
)}
</Stack>
</Grid>
</Grid>
);
}
export default UserDisplay;

View file

@ -0,0 +1,10 @@
import { Link } from 'react-router-dom';
import { styled } from '@mui/material/styles';
const BaseLink = styled(Link)(() => ({
textDecoration: 'none',
color: 'inherit',
}));
export default BaseLink;

View file

@ -0,0 +1,24 @@
import React from 'react';
import { Box, Grid, Stack } from '@mui/material';
function PageBase({ children }) {
return (
<Box sx={{ flexGrow: 1 }}>
<Grid
container
spacing={2}
alignItems='center'
style={{ height: '95vh' }}
>
<Grid item xs />
<Grid item xs={11}>
<Stack spacing={2}>{children}</Stack>
</Grid>
<Grid item xs />
</Grid>
</Box>
);
}
export default PageBase;

View file

@ -0,0 +1,24 @@
import React from 'react';
import { Paper, Typography } from '@mui/material';
import { styled } from '@mui/material/styles';
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
function StackItem({ text, children }) {
return (
<Item>
<Typography variant='h6'>{text}</Typography>
<hr />
{children}
</Item>
);
}
export default StackItem;

View file

@ -0,0 +1,9 @@
import axios from 'axios';
function axiosGet(url, params) {
return axios.get(url, {
params,
});
}
export default axiosGet;

View file

@ -0,0 +1,47 @@
import axios from 'axios';
async function getUserForgeData(username, profileID) {
const data = await axios
.get(`https://sky.shiiyu.moe/api/v2/profile/${username}`)
.then((res) => res.data);
const forgeData = data.profiles[profileID]?.data?.mining?.forge;
return forgeData;
}
async function getAuctions() {
const totalPages = await axios
.get('https://api.hypixel.net/skyblock/auctions')
.then((res) => res.data.totalPages);
const auctionPages = await Promise.allSettled(
Array.from(Array(totalPages)).map((_, i) => {
return axios.get(`https://api.hypixel.net/skyblock/auctions`, {
params: { page: i },
});
})
).then((responses) =>
responses.filter((response) => response.status === 'fulfilled')
);
return auctionPages
.reduce((acc, response) => acc.concat(response.value.data.auctions), [])
.filter((auction) => auction.bin);
}
async function getBazaar() {
const data = await axios
.get('https://api.hypixel.net/skyblock/bazaar')
.then((res) => res.data.products);
return Object.keys(data).reduce((acc, key) => acc.concat(data[key]), []);
}
async function getItems() {
return await axios
.get('https://api.hypixel.net/resources/skyblock/items')
.then((res) => res.data.items);
}
export { getAuctions, getBazaar, getItems, getUserForgeData };

1180
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,7 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "npm start -ws"
"start": "npm start -ws",
"build": "npm run build -ws"
},
"author": "",
"license": "ISC",
@ -12,7 +13,6 @@
"client"
],
"dependencies": {
"create-react-app": "^5.0.0",
"react-router-dom": "^6.3.0"
"create-react-app": "^5.0.0"
}
}