Completed v1.0.0
This commit is contained in:
parent
3fd0b8d0f1
commit
0af8b77295
18 changed files with 1902 additions and 281 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -169,6 +169,7 @@ node_modules
|
|||
psd
|
||||
thumb
|
||||
sketch
|
||||
**/build
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
44
client/public/data.json
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
69
client/src/App.jsx
Normal 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;
|
||||
109
client/src/components/BitsToCoin.jsx
Normal file
109
client/src/components/BitsToCoin.jsx
Normal 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;
|
||||
39
client/src/components/MainPage.jsx
Normal file
39
client/src/components/MainPage.jsx
Normal 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;
|
||||
91
client/src/components/NPCUser.jsx
Normal file
91
client/src/components/NPCUser.jsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
120
client/src/components/UserDisplay.jsx
Normal file
120
client/src/components/UserDisplay.jsx
Normal 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;
|
||||
10
client/src/components/utils/BaseLink.jsx
Normal file
10
client/src/components/utils/BaseLink.jsx
Normal 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;
|
||||
24
client/src/components/utils/PageBase.jsx
Normal file
24
client/src/components/utils/PageBase.jsx
Normal 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;
|
||||
24
client/src/components/utils/StackItem.jsx
Normal file
24
client/src/components/utils/StackItem.jsx
Normal 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;
|
||||
9
client/src/utils/axiosGet.js
Normal file
9
client/src/utils/axiosGet.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import axios from 'axios';
|
||||
|
||||
function axiosGet(url, params) {
|
||||
return axios.get(url, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export default axiosGet;
|
||||
47
client/src/utils/getData.js
Normal file
47
client/src/utils/getData.js
Normal 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
1180
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue