First Commit
73
README.md
|
|
@ -1,70 +1,7 @@
|
|||
# Getting Started with Create React App
|
||||
# Prittee
|
||||

|
||||
Prittee is an unofficial, experimental alternate front end for Pithee, the site Jacksfilms recent launched. I made Prittee because I noticed some small UI/UX issues I thought could be fixed to improve the user experience of the site (most notably, the time it takes to load more posts after voting for one).
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
Prittee has some limitations due to it being an unofficial site, such as the fact that you can only be logged in for an hour at a time, so I don't expect this to replace the official Pithee site for people's pith-ing needs. This was mostly made as an exercise for me.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn 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.
|
||||
|
||||
### `yarn 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.
|
||||
|
||||
### `yarn 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.
|
||||
|
||||
### `yarn 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)
|
||||
|
||||
### `yarn 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)
|
||||
The site is made in React, using React Router v6.
|
||||
BIN
image-1.png
Normal file
|
After Width: | Height: | Size: 237 KiB |
BIN
image-2.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
image.png
Normal file
|
After Width: | Height: | Size: 260 KiB |
11
package.json
|
|
@ -3,11 +3,17 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/icons-material": "^5.15.14",
|
||||
"@mui/lab": "^5.0.0-alpha.169",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.0"
|
||||
},
|
||||
|
|
@ -34,5 +40,10 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -3,11 +3,11 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="An unofficial alternate front end for Pithee"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Prittee - A Pithee Client</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 7.1 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "Prittee",
|
||||
"name": "An unofficial front end for Pithee",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
|
|
|||
45
src/App.css
|
|
@ -1,38 +1,21 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
|
||||
#winnerPostAuthor {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
.prevent-select {
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none; /* Standard syntax */
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
37
src/App.js
|
|
@ -1,24 +1,29 @@
|
|||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import { Box, Container } from '@mui/material';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import Header from './Components/Header';
|
||||
import Footer from './Components/Footer';
|
||||
|
||||
// TODO:
|
||||
// - Leaderboard past #25 (pagination)
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
<Container>
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
className='h-screen'
|
||||
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
<Header />
|
||||
<Box width={480} id='gameContainer' className='grow'>
|
||||
<Outlet />
|
||||
</Box>
|
||||
<Footer/>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
38
src/Components/Countdown.jsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export default function Countdown({ until, onEnd }) {
|
||||
|
||||
const t1 = new Date()
|
||||
const t2 = new Date(until) // 10 minutes after win time
|
||||
const msUntil = (t2.getTime()+10*60000) - t1.getTime();
|
||||
const secondsUntil = Math.floor(msUntil / 1000)
|
||||
const [seconds, setSeconds] = useState(secondsUntil)
|
||||
useEffect(() => {
|
||||
var interval = setInterval(() => {
|
||||
setSeconds(s => s-1)
|
||||
if (seconds < 1) {
|
||||
clearInterval(interval)
|
||||
onEnd()
|
||||
}
|
||||
}, 1000);
|
||||
return function cleanup() {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [seconds])
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (time <= 0) {
|
||||
return '0:00'
|
||||
}
|
||||
let m = Math.floor(time / 60)
|
||||
let s = time - (m * 60)
|
||||
let lead = s < 10 ? '0' : ''
|
||||
return `${m}:${lead+s}`
|
||||
}
|
||||
return (
|
||||
<Box className='prevent-select'>
|
||||
{formatTime(seconds)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
10
src/Components/Footer.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Container, Box } from "@mui/material";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<Box width={600} className='flex flex-row justify-around text-sm text-neutral-500 border-t-2 border-neutral-600 p-4 mt-4'>
|
||||
<a className="hover:text-neutral-300" href="https://github.com/gabehf/Prittee" target="_blank">View the source on GitHub</a>
|
||||
<a className="hover:text-neutral-300" href="https://forms.gle/a8BkH6yUDnzLJD938" target="_blank">Report a bug</a>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
29
src/Components/Header.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Box, Container } from "@mui/material"
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { AccountBoxOutlined, PostAdd, LeaderboardOutlined } from '@mui/icons-material';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function Header() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<Container className='mt-6 mb-4'>
|
||||
<Box id="header" className="mb-2 text-center hover:cursor-pointer" onClick={() => navigate('/')}>
|
||||
<h1 className='text-4xl font-bold'>Prittee</h1>
|
||||
<p className="text-md text-slate-300">An <span className='text-sky-400 italic'>unofficial</span> alternate front end for Pithee</p>
|
||||
</Box>
|
||||
<Box className='flex flex-row justify-between m-auto mb-6' width={125}>
|
||||
<Box>
|
||||
<NavLink to='/me'><AccountBoxOutlined /></NavLink>
|
||||
</Box>
|
||||
<Box>
|
||||
<NavLink to='/leaderboard'><LeaderboardOutlined /></NavLink>
|
||||
</Box>
|
||||
<Box>
|
||||
<NavLink to='/post'><PostAdd /></NavLink>
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
21
src/Components/Leaderboard.jsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export default function Leaderboard({ users }) {
|
||||
let i = 0
|
||||
return (
|
||||
<Box maxHeight={500} className='overflow-y-auto no-scrollbar'>
|
||||
{users.map((user) => {
|
||||
i++
|
||||
return (
|
||||
<Box className='flex flex-row justify-between border-b-2 p-2 mb-3'>
|
||||
<Box className='flex flex-row gap-8'>
|
||||
<Box>{i}.</Box>
|
||||
<img src={user.avatar_url} alt={user.player_name} width={35} height={35}/>
|
||||
</Box>
|
||||
<Box>{user.player_name}</Box>
|
||||
<Box className='text-end'>{user.total_points}</Box>
|
||||
</Box>
|
||||
)})}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
9
src/Components/LeaderboardSkeleton.jsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { LinearProgress, Box } from "@mui/material";
|
||||
|
||||
export default function LeaderboardSkeleton() {
|
||||
return (
|
||||
<Box maxHeight={500} className='overflow-y-auto no-scrollbar'>
|
||||
<LinearProgress color="inherit" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
58
src/Components/Post.jsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { LinearProgress } from '@mui/material'
|
||||
import { Key, Star } from "@mui/icons-material";
|
||||
|
||||
export default function Post({ text, tier, index, id, onClick }) {
|
||||
const renderStars = () => {
|
||||
const arr = []
|
||||
for (let i = 0; i < tier; i++) {
|
||||
arr.push(
|
||||
<Star fontSize='small' />
|
||||
)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
if (text === '') {
|
||||
return (
|
||||
<Box className="
|
||||
transition-all
|
||||
duration-300
|
||||
bg-gradient-to-tl
|
||||
from-indigo-900
|
||||
via-indigo-600
|
||||
to-purple-500
|
||||
bg-size-200
|
||||
bg-pos-0
|
||||
hover:bg-pos-100
|
||||
hover:cursor-pointer
|
||||
p-8" height={225}>
|
||||
<LinearProgress sx={{
|
||||
backgroundColor: 'white',
|
||||
'& .MuiLinearProgress-bar': {
|
||||
backgroundColor: 'indigo'
|
||||
}
|
||||
}} className="opacity-25"/>
|
||||
</Box>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Box className="
|
||||
transition-all
|
||||
duration-300
|
||||
bg-gradient-to-tl
|
||||
from-indigo-900
|
||||
via-indigo-600
|
||||
to-purple-500
|
||||
bg-size-200
|
||||
bg-pos-0
|
||||
hover:bg-pos-100
|
||||
hover:cursor-pointer
|
||||
p-8" height={225} onClick={() => onClick({post_text: text, index: index, tier: tier, id: id})}>
|
||||
<Box sx={{marginTop: -2, marginBottom: 1, marginLeft: -2, display: 'flex'}}>
|
||||
{renderStars()}
|
||||
</Box>
|
||||
<p className='text-clip overflow-hidden prevent-select'>{text}</p>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
}
|
||||
164
src/Components/PostGrid.jsx
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { Grid } from '@mui/material';
|
||||
import Post from './Post';
|
||||
import { declareVictor, fetchPosts, pushPostScore } from '../utils'
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Queue from '../queue'
|
||||
import { getFullContestants, lenContestants, rmContestant } from '../contestants';
|
||||
|
||||
const postsSkeleton = [
|
||||
{
|
||||
post_text: '',
|
||||
index: 0,
|
||||
id: '1',
|
||||
tier: 0,
|
||||
},
|
||||
{
|
||||
post_text: '',
|
||||
index: 1,
|
||||
id: '2',
|
||||
tier: 0,
|
||||
},
|
||||
{
|
||||
post_text: '',
|
||||
index: 2,
|
||||
id: '3',
|
||||
tier: 0,
|
||||
},
|
||||
{
|
||||
post_text: '',
|
||||
index: 3,
|
||||
id: '4',
|
||||
tier: 0,
|
||||
},
|
||||
]
|
||||
|
||||
const PostQueue = new Queue()
|
||||
|
||||
export default function PostGrid() {
|
||||
const navigate = useNavigate()
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [posts, setPosts] = useState(postsSkeleton)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// every time data state changes i.e. every time a user clicks on post,
|
||||
// update posts with their selection
|
||||
// note: this also will load the initial posts
|
||||
useEffect(()=> {
|
||||
//
|
||||
// rebuild post array with kept post
|
||||
// render new array
|
||||
updatePosts(selected)
|
||||
}, [selected])
|
||||
|
||||
// handler passed to Post children to let us know when one has been selected
|
||||
const selectPost = (post) => {
|
||||
post.tier += 1
|
||||
if (post.tier == 4) { // triggers on a showdown winner
|
||||
declareVictor(post)
|
||||
setSelected(null)
|
||||
// return early because we already handled selecting a showdown winner
|
||||
return
|
||||
}
|
||||
// one of these if statements will trigger when a
|
||||
// post has reached the end of its ranking,
|
||||
// or if it has reached max-tier respectively
|
||||
if (selected != null && post.id != selected.id && selected.tier != 3) {
|
||||
pushPostScore(selected)
|
||||
}
|
||||
if (post.tier == 3){
|
||||
pushPostScore(post)
|
||||
}
|
||||
// triggers when it's time for a showdown
|
||||
if (lenContestants() >= 4) {
|
||||
setPosts(getFullContestants())
|
||||
// return early so as to not trigger the updatePosts logic and
|
||||
// instead render the showdown
|
||||
return
|
||||
}
|
||||
setSelected(post)
|
||||
}
|
||||
|
||||
// handles updating the post state
|
||||
const refreshPosts = (keep) => {
|
||||
// this line prevents trying to refresh posts on mount, when
|
||||
// no posts have actually been loaded yet
|
||||
if (PostQueue.size() < 4) { return }
|
||||
const arr = []
|
||||
for (var i = 0; i < 4; i++) {
|
||||
if (keep != null && keep.index == i && keep.tier < 3) {
|
||||
arr.push(keep)
|
||||
} else {
|
||||
let p = PostQueue.pop()
|
||||
arr.push(
|
||||
{
|
||||
post_text: p.post_text,
|
||||
index: i,
|
||||
id: p.post_id,
|
||||
tier: 0,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
setPosts(arr)
|
||||
}
|
||||
|
||||
// handles refilling the queue when needed, then calls refreshPosts
|
||||
const updatePosts = (keep) => {
|
||||
console.log(`loading: ${loading}`)
|
||||
// if the queue is really low, we want to stop the user from making any more
|
||||
// selections to let the queue reload
|
||||
if (PostQueue.size() <= 4) {
|
||||
if (loading) {
|
||||
console.log('user is clicking too fast!')
|
||||
// if we are already loading in new posts and the user
|
||||
// is basically spam clicking, just return right away to
|
||||
// prevent it from requesting more posts
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
console.log('loading last-second posts...')
|
||||
fetchPosts().then((r) => {
|
||||
if (r == null) {
|
||||
} else {
|
||||
r.forEach(npost => {
|
||||
PostQueue.push(npost)
|
||||
});
|
||||
refreshPosts(keep)
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
} else if (PostQueue.size() <= 11 && !loading) {
|
||||
// somewhat low queue -> fetch more posts in the background
|
||||
setLoading(true)
|
||||
console.log('loading more posts...')
|
||||
fetchPosts().then((r) => {
|
||||
if (r == null) {
|
||||
alert("Your token has expired. Please re-enter your information to keep using Prittee.")
|
||||
navigate('/login')
|
||||
} else {
|
||||
r.forEach(npost => {
|
||||
PostQueue.push(npost)
|
||||
});
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
refreshPosts(keep)
|
||||
} else {
|
||||
// full queue -> keep going as normal
|
||||
refreshPosts(keep)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{posts.map((p) => {
|
||||
return(
|
||||
<Grid item xs={6} key={'post-grid-'+p.index}>
|
||||
<Post text={p.post_text} key={p.id} id={p.id} tier={p.tier} index={p.index} onClick={selectPost}/>
|
||||
</Grid>
|
||||
)})}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
12
src/Components/PreviousWinner.jsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export default function PreviousWinner({ post }) {
|
||||
let dateString = new Date(post.won_at).toLocaleString()
|
||||
return (
|
||||
<Box className='border-b-2 border-white p-4'>
|
||||
<Box>{post.post_text}</Box>
|
||||
<Box className='text-sm'>by {post.player_name}</Box>
|
||||
<Box className='text-sm'>Won: {dateString}</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
14
src/Components/UserPost.jsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export default function UserPost({ post }) {
|
||||
let dateString = new Date(post.created_at).toLocaleString()
|
||||
return (
|
||||
<Box className='border-b-2 border-white p-4'>
|
||||
<Box>{post.post_text}</Box>
|
||||
<Box className='text-sm'>Points: {post.points}</Box>
|
||||
<Box className='text-sm'>Posted: {dateString}</Box>
|
||||
{/* I actually don't know if won_at is the right key because i haven't won yet LOL */}
|
||||
{post.won_at ? <Box className='text-sm'>Posted: {dateString}</Box> : <></>}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
15
src/Components/WinArchive.jsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Box } from "@mui/material";
|
||||
import PreviousWinner from "./PreviousWinner";
|
||||
|
||||
export default function WinArchive({ data }) {
|
||||
return(
|
||||
<Box id="lastWinner" className="
|
||||
p-4
|
||||
pt-0
|
||||
mb-6
|
||||
overflow-y-auto
|
||||
no-scrollbar" height={434}>
|
||||
{data.map((post) => <PreviousWinner post={post}/>)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
33
src/Pages/CreatePost.jsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { TextField } from '@mui/material'
|
||||
import Box from '@mui/material/Box'
|
||||
import LoadingButton from '@mui/lab/LoadingButton'
|
||||
import { useState } from 'react'
|
||||
import { insertPost } from '../utils'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export default function CreatePost() {
|
||||
const [text, setText] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const submitHandler = () => {
|
||||
if (text.length > 80) {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
insertPost(text)
|
||||
.then(() => {
|
||||
navigate('/')
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Box className='flex flex-col align-end m-auto' width={300}>
|
||||
<TextField color={text.length > 80 ? 'error' : 'primary'} label="Make a post" multiline rows={3} margin='normal' onChange={(e) => setText(e.target.value)}/>
|
||||
<div className={text.length > 80 ? 'text-red-400' : 'text-neutral-300'}>{text.length}/80</div>
|
||||
<LoadingButton loading={loading} className='grow-0' onClick={submitHandler}>Submit</LoadingButton>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
//i saw a bank that offered "24 hour service" and I thought "it takes that long??"
|
||||
52
src/Pages/Index.jsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Box } from '@mui/material';
|
||||
import PostGrid from '../Components/PostGrid'
|
||||
import { useLoaderData } from 'react-router-dom';
|
||||
import Countdown from '../Components/Countdown';
|
||||
import { useState } from 'react';
|
||||
import { loadCurrentWinner } from '../utils';
|
||||
import WinArchive from '../Components/WinArchive';
|
||||
|
||||
export default function Index({ cw, wa }) {
|
||||
|
||||
const data = useLoaderData()
|
||||
const [winArchive, setWinArchive] = useState(data[0])
|
||||
const [currentWinner, setCurrentWinner] = useState(data[1][0]) // why is the current winner api response an array??
|
||||
const [winnerTime, setWinnerTime] = useState(new Date(currentWinner.won_at)) // why is the current winner api response an array??
|
||||
const [showWinArchive, setShowWinArchive] = useState(false)
|
||||
|
||||
const reloadWinner = async () => {
|
||||
console.log('loading new winner')
|
||||
loadCurrentWinner().then(r => {
|
||||
setCurrentWinner(r[0])
|
||||
setWinnerTime(new Date(currentWinner.won_at))
|
||||
})
|
||||
}
|
||||
|
||||
const toggleWinArchive = () => {
|
||||
setShowWinArchive(s => !s)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Box id="lastWinner" className="
|
||||
bg-gradient-to-tl
|
||||
transition-all
|
||||
duration-300
|
||||
from-purple-500
|
||||
via-sky-500
|
||||
to-emerald-500
|
||||
bg-size-200
|
||||
bg-pos-0
|
||||
hover:bg-pos-100
|
||||
hover:cursor-pointer
|
||||
p-8
|
||||
mb-3" onClick={() => toggleWinArchive()}>
|
||||
<p className="post-text pb-2 prevent-select" id="winnerPostText">{currentWinner.post_text}</p>
|
||||
<p className="post-author prevent-select" id="winnerPostAuthor">by {currentWinner.player_name}</p>
|
||||
<Countdown key={String(winnerTime)} until={winnerTime} onEnd={reloadWinner}/>
|
||||
</Box>
|
||||
<Box id="posts">
|
||||
{showWinArchive ? <WinArchive data={winArchive}></WinArchive>: <PostGrid />}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
16
src/Pages/LeaderboardPage.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Box } from "@mui/material";
|
||||
import { useLoaderData, Await } from "react-router-dom";
|
||||
import React from "react";
|
||||
import Leaderboard from "../Components/Leaderboard";
|
||||
import LeaderboardSkeleton from "../Components/LeaderboardSkeleton";
|
||||
|
||||
export default function LeaderboardPage() {
|
||||
const { data } = useLoaderData()
|
||||
return (
|
||||
<React.Suspense fallback={<LeaderboardSkeleton />}>
|
||||
<Await resolve={data}>
|
||||
{(users) => <Leaderboard users={users} />}
|
||||
</Await>
|
||||
</React.Suspense>
|
||||
)
|
||||
}
|
||||
69
src/Pages/Login.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import TextField from '@mui/material/TextField'
|
||||
import LoadingButton from '@mui/lab/LoadingButton'
|
||||
import Box from '@mui/material/Box'
|
||||
import { Container } from '@mui/material'
|
||||
import Link from '@mui/material/Link'
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { loadUser } from '../utils'
|
||||
|
||||
// TODO: change api key and token inputs to one input for Copy -> Request Headers
|
||||
|
||||
export default function Login() {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [ token, setToken ] = useState('')
|
||||
const [ apikey, setApiKey ] = useState(localStorage.getItem("apikey"))
|
||||
const [ failText, setFailText ] = useState('')
|
||||
const [ apiKeyErr, setApiKeyErr ] = useState(false)
|
||||
const [ tokenErr, setTokenErr ] = useState(false)
|
||||
const [ loading, setLoading ] = useState(false)
|
||||
|
||||
const onTokenChange = (e) => setToken(e.target.value);
|
||||
const onApiKeyChange = (e) => setApiKey(e.target.value);
|
||||
|
||||
const addUserToken = async () => {
|
||||
setLoading(true)
|
||||
// reset error values
|
||||
setTokenErr(false)
|
||||
setApiKeyErr(false)
|
||||
// ensure the bearer token provided is valid
|
||||
localStorage.setItem('token', token)
|
||||
localStorage.setItem('apikey', apikey)
|
||||
loadUser().then((r) => {
|
||||
setLoading(false)
|
||||
if (r.success) {
|
||||
navigate('/')
|
||||
} else {
|
||||
if (r.apikeyInvalid) {
|
||||
setApiKeyErr(true)
|
||||
}
|
||||
if (r.tokenInvalid) {
|
||||
setTokenErr(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight="100vh"
|
||||
>
|
||||
<Container>
|
||||
<h1 className='text-center mb-12 text-xl'>Prittee is an <span className='text-red-400 italic'>experimental</span> alternate front end for Pithee<br/>
|
||||
Before using it is recommended you <Link href='https://zircon-stoplight-fbb.notion.site/Prittee-Introduction-0e418deda00242ebb6026d074b397629'>read the introduction</Link> to learn about Prittee's limitations</h1>
|
||||
<p className='text-center'>Add your Pithee api key and authorization token to start using Prittee.<br/>
|
||||
You can find out how to get your info <Link href="https://zircon-stoplight-fbb.notion.site/Prittee-Introduction-0e418deda00242ebb6026d074b397629">here</Link>.</p>
|
||||
<Box component="div" display="flex" flexDirection="row" alignItems="center" justifyContent="center" gap={4} className='mt-6'>
|
||||
<TextField id="apikey" error={apiKeyErr} onChange={onApiKeyChange} label="API Key" variant="outlined" value={localStorage.getItem("apikey")} />
|
||||
<TextField id="auth-token" error={tokenErr} helperText={failText} onChange={onTokenChange} label="Authorization Token" variant="outlined" />
|
||||
<LoadingButton loading={loading} variant="text" size="large" onClick={addUserToken}>Let's Go</LoadingButton>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
61
src/Pages/Profile.jsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import Header from "../Components/Header";
|
||||
import Box from '@mui/material/Box'
|
||||
import { LinearProgress, CircularProgress } from "@mui/material";
|
||||
import { useLoaderData, Await } from "react-router-dom";
|
||||
import React from "react";
|
||||
import UserPost from "../Components/UserPost";
|
||||
|
||||
export default function Profile() {
|
||||
const { user, posts } = useLoaderData()
|
||||
|
||||
const loadingFallback = () => {
|
||||
return (
|
||||
<Box className='flex flex-col items-center'>
|
||||
<Box className='flex flex-row justify-around items-center' width={480} height={140}>
|
||||
<Box maxWidth={150}>
|
||||
<CircularProgress color="inherit"/>
|
||||
</Box>
|
||||
<Box width={100}>
|
||||
<LinearProgress color="inherit" />
|
||||
<br/>
|
||||
<LinearProgress color="inherit" />
|
||||
<br/>
|
||||
<LinearProgress color="inherit" />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box maxHeight={400} width={400} className='mt-4 overflow-y-auto no-scrollbar'>
|
||||
<br/>
|
||||
<br/>
|
||||
<LinearProgress color="inherit" />
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<React.Suspense fallback={loadingFallback()}>
|
||||
<Await resolve={Promise.all([user, posts]).then(value => value)}>
|
||||
{(value) => {
|
||||
let [ user, posts ] = value
|
||||
user = user.user
|
||||
return(
|
||||
<Box className='flex flex-col items-center'>
|
||||
<Box className='flex flex-row justify-around items-center' width={480}>
|
||||
<Box maxWidth={150}>
|
||||
<img src={user.avatar_url} alt={`${user.player_name}`} />
|
||||
</Box>
|
||||
<Box>
|
||||
<h2 className="text-2xl font-bold mb-2">{user.player_name}</h2>
|
||||
<p>Total Points: {user.total_points}</p>
|
||||
<p>Posts: {posts.length}</p>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box maxHeight={400} className='mt-4 overflow-y-auto no-scrollbar'>
|
||||
{posts.map((post) => <UserPost post={post} />)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
</Await>
|
||||
</React.Suspense>
|
||||
)
|
||||
}
|
||||
31
src/contestants.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
let contestants = {}
|
||||
|
||||
const addContestant = function(post) {
|
||||
contestants[post.id] = post
|
||||
}
|
||||
|
||||
const rmContestant = function(post) {
|
||||
delete contestants[post.id]
|
||||
}
|
||||
|
||||
const lenContestants = function() {
|
||||
return Object.keys(contestants).length
|
||||
}
|
||||
|
||||
const getContestants = function() {
|
||||
return Object.keys(contestants)
|
||||
}
|
||||
|
||||
const getFullContestants = function() {
|
||||
// when fetching the full contestants, the indices need
|
||||
// to be reset to prevent duplication in the React renderer
|
||||
let raw = Object.values(contestants)
|
||||
let i = 0
|
||||
for (let post of raw) {
|
||||
post.index = i
|
||||
i++
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
export { addContestant, rmContestant, lenContestants, getContestants, getFullContestants }
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #141113;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: white;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.centralContent {
|
||||
margin: auto;
|
||||
}
|
||||
|
|
|
|||
87
src/index.js
|
|
@ -2,13 +2,94 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import Login from './Pages/Login'
|
||||
import Profile from './Pages/Profile'
|
||||
import CreatePost from './Pages/CreatePost'
|
||||
import LeaderboardPage from './Pages/LeaderboardPage';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import {
|
||||
createBrowserRouter,
|
||||
redirect,
|
||||
RouterProvider,
|
||||
defer
|
||||
} from 'react-router-dom'
|
||||
import { createTheme, ThemeProvider } from '@mui/material';
|
||||
import { lightBlue, cyan, orange, green, red, purple } from '@mui/material/colors';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { loadCurrentWinner, loadLeaderboardPage, loadUser, loadUserPosts, loadWinArchive } from './utils';
|
||||
import Index from './Pages/Index';
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: cyan,
|
||||
secondary: purple,
|
||||
error: red,
|
||||
warning: orange,
|
||||
info: lightBlue,
|
||||
success: green,
|
||||
},
|
||||
typography: {
|
||||
fontSize: 16,
|
||||
}
|
||||
})
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <App />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <Index />,
|
||||
loader: async () => {
|
||||
let c = await loadCurrentWinner()
|
||||
let a = await loadWinArchive()
|
||||
if (!c || !a) {
|
||||
return redirect('/login')
|
||||
}
|
||||
|
||||
return new Promise((resolve) => resolve([a, c]))
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/me',
|
||||
element: <Profile />,
|
||||
loader: async () => {
|
||||
return defer({
|
||||
user: loadUser(),
|
||||
posts: loadUserPosts()
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/post',
|
||||
element: <CreatePost />
|
||||
},
|
||||
{
|
||||
path: '/leaderboard',
|
||||
element: <LeaderboardPage />,
|
||||
loader: async () => {
|
||||
return defer({
|
||||
data: loadLeaderboardPage(1),
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
element: <Login />,
|
||||
},
|
||||
])
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
<>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
28
src/queue.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
class Queue {
|
||||
constructor() {
|
||||
this.queue = [];
|
||||
}
|
||||
|
||||
push(element) {
|
||||
this.queue.push(element);
|
||||
}
|
||||
|
||||
pop() {
|
||||
return this.queue.shift();
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this.queue[0];
|
||||
}
|
||||
|
||||
print() {
|
||||
console.log(this.queue)
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.queue.length
|
||||
}
|
||||
}
|
||||
|
||||
export default Queue
|
||||
|
||||
218
src/utils.js
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
// NOTE: this file is poorly named
|
||||
import { addContestant, getContestants, rmContestant } from "./contestants"
|
||||
|
||||
const userDataUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/get_player_data'
|
||||
const postsUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/get_posts'
|
||||
const playerPostsUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/get_player_posts'
|
||||
const currentWinnerUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/current_winner?select=*'
|
||||
const winnerArchiveUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/win_archive_view?select=*&limit=50'
|
||||
const ygsUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/ygs'
|
||||
const contestantsUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/update_contestants'
|
||||
const victorUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/update_recent_showdown'
|
||||
const leaderboardUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/get_paginated_leaderboard'
|
||||
const insertPostUrl = 'https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/insert_post'
|
||||
|
||||
|
||||
const tierIds = [
|
||||
'1878f11f-782b-43fe-9aa3-cd34e989b174',
|
||||
'629558be-c63d-4255-9bf5-1e98826fa3de',
|
||||
'402261d8-4db8-4e24-8886-90dc6da6fcd1'
|
||||
]
|
||||
|
||||
|
||||
const loadUser = async function() {
|
||||
return new Promise((resolve) => {
|
||||
fetch('https://oqutjaxxxzzbjtyrfoka.supabase.co/rest/v1/rpc/get_player_data', {
|
||||
method: 'POST', // why post?
|
||||
headers: {
|
||||
"APIKey": localStorage.getItem("apikey"),
|
||||
"Authorization": localStorage.getItem("token")
|
||||
}
|
||||
}).then(r => r.json().then(data => ({status: r.status, body: data})))
|
||||
.then((r) => {
|
||||
// note: when an empty bearer token is provided, the server responds with 200 OK with an empty array as
|
||||
// the body instead of the expected 401. This is a bug with Pithee that we need to account for!
|
||||
if (r.body.length > 0) {
|
||||
resolve({
|
||||
success: true,
|
||||
user: r.body[0]
|
||||
})
|
||||
} else if (r.status == 200) {
|
||||
resolve({
|
||||
success: false,
|
||||
apikeyInvalid: false,
|
||||
tokenInvalid: true,
|
||||
})
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
apikeyInvalid: true,
|
||||
tokenInvalid: true,
|
||||
})
|
||||
}
|
||||
}).catch((err) => {
|
||||
alert(err)
|
||||
resolve({
|
||||
success: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const loadUserPosts = async function () {
|
||||
return makePitheeApiCall(playerPostsUrl, 'POST', null) // why POST?
|
||||
}
|
||||
|
||||
const loadCurrentWinner = async function() {
|
||||
return makePitheeApiCall(currentWinnerUrl, 'GET', null)
|
||||
}
|
||||
|
||||
const loadWinArchive = async function() {
|
||||
return makePitheeApiCall(winnerArchiveUrl, 'GET', null)
|
||||
}
|
||||
|
||||
const fetchPosts = async function() {
|
||||
return makePitheeApiCall(postsUrl, 'POST', JSON.stringify({post_quantity: 20}))
|
||||
}
|
||||
|
||||
const loadLeaderboardPage = async function(page) {
|
||||
return makePitheeApiCall(leaderboardUrl, 'POST', JSON.stringify({
|
||||
page_number: page,
|
||||
page_size: 25
|
||||
}))
|
||||
}
|
||||
|
||||
const updateContestants = async function() {
|
||||
return makePitheeNoContentApiCall(contestantsUrl, 'POST', JSON.stringify({contestants: getContestants()}))
|
||||
}
|
||||
|
||||
const showdownVictor = async function(victor_id) {
|
||||
return makePitheeNoContentApiCall(victorUrl, 'POST', JSON.stringify({showdown_victor: victor_id}))
|
||||
}
|
||||
|
||||
const pushPostScore = async function(post) {
|
||||
if (post.tier < 1 || post.tier > 3) {
|
||||
console.log('invalid tier value')
|
||||
return
|
||||
}
|
||||
|
||||
// just going to leave this unattended. sure I should check if the call
|
||||
// failed, but I won't. Maybe I'll fix it later
|
||||
makePitheeNoContentApiCall(ygsUrl, 'POST', JSON.stringify({
|
||||
jacks: post.id,
|
||||
films: tierIds[post.tier-1]
|
||||
})) // good one, guys
|
||||
|
||||
// also update contestants for a three star post
|
||||
// what confuses me is that the contestants for a showdown are stored
|
||||
// client-side, but we still need to push them to the server. why?
|
||||
// if you ask me, the contestants should be all on the server or on
|
||||
// the client. IMO all on the server is better because I don't think
|
||||
// showdown progress is retained upon refresh or login/logout which is silly
|
||||
if (post.tier === 3) {
|
||||
addContestant(post)
|
||||
updateContestants()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const declareVictor = function (post) {
|
||||
// upon showdown victor, another ygs call is made with the tier3 uuid,
|
||||
// as well as a call to update_recent_showdown
|
||||
// then, ONLY the victor is removed from the contestants
|
||||
// this means that the very next post to reach tier3 triggers a showdown
|
||||
// is this intended behavior? idk, but I will mirror it.
|
||||
makePitheeNoContentApiCall(ygsUrl, 'POST', JSON.stringify({
|
||||
jacks: post.id,
|
||||
films: tierIds[2]
|
||||
})) // good one, guys
|
||||
showdownVictor(post.id)
|
||||
rmContestant(post)
|
||||
updateContestants()
|
||||
}
|
||||
|
||||
const insertPost = function (postText) {
|
||||
// the /insert_post endpoint weirdly responds with a string instead of
|
||||
// JSON on success so I can't use the regular makePitheeApiCall function
|
||||
return makePitheeNoContentApiCall(insertPostUrl, 'POST', JSON.stringify({
|
||||
post_text: postText
|
||||
}))
|
||||
}
|
||||
|
||||
// /ygs and /update_contestants endpoints do not serve JSON responses so we need this func
|
||||
// ALSO both of those endpoints return 204 OK when a null authorization is given... ???
|
||||
const makePitheeNoContentApiCall = async function(url, method, body) {
|
||||
return new Promise((resolve) => {
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"apikey": localStorage.getItem('apikey'),
|
||||
"authorization": localStorage.getItem('token'),
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: body
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status >= 200 && r.status <= 299) { // OK range
|
||||
resolve(r.body)
|
||||
} else if (r.status === 401) {
|
||||
alert("Your token has expired. Please re-enter your information to keep using Prittee.")
|
||||
window.location.href = '/login'
|
||||
} else {
|
||||
// TODO: this is little information for the client.
|
||||
// the server could be responding with a 401 due to expired
|
||||
// key (expected behavior) OR it could be a 500 server error and the client
|
||||
// will not know the difference. This should be fixed!
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// the API annoyingly returns a 200 OK with an empty array response body
|
||||
// when called with a null api key...
|
||||
const makePitheeApiCall = async function(url, method, body) {
|
||||
return new Promise((resolve) => {
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
"apikey": localStorage.getItem('apikey'),
|
||||
"authorization": localStorage.getItem('token'),
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: body
|
||||
})
|
||||
.then(r => r.json().then(data => ({status: r.status, body: data})))
|
||||
.then((r) => {
|
||||
if (r.status >= 200 && r.status <= 299) { // OK range
|
||||
// check if empty array is given (token is null)
|
||||
if (r.body.length == 0) {
|
||||
alert("Your token has expired. Please re-enter your information to keep using Prittee.")
|
||||
window.location.href = '/login'
|
||||
}
|
||||
resolve(r.body)
|
||||
} else if (r.status === 401) {
|
||||
alert("Your token has expired. Please re-enter your information to keep using Prittee.")
|
||||
window.location.href = '/login'
|
||||
} else {
|
||||
// TODO: this is little information for the client.
|
||||
// the server could be responding with a 401 due to expired
|
||||
// key (expected behavior) OR it could be a 500 server error and the client
|
||||
// will not know the difference. This should be fixed!
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
loadUser,
|
||||
loadWinArchive,
|
||||
loadCurrentWinner,
|
||||
fetchPosts,
|
||||
pushPostScore,
|
||||
declareVictor,
|
||||
loadUserPosts,
|
||||
loadLeaderboardPage,
|
||||
insertPost,
|
||||
}
|
||||
16
tailwind.config.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundSize: {
|
||||
'size-200': '200% 200%',
|
||||
},
|
||||
backgroundPosition: {
|
||||
'pos-0': '0% 0%',
|
||||
'pos-100': '100% 100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
337
yarn.lock
|
|
@ -174,7 +174,7 @@
|
|||
dependencies:
|
||||
"@babel/types" "^7.23.0"
|
||||
|
||||
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3":
|
||||
"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3":
|
||||
version "7.24.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128"
|
||||
integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==
|
||||
|
|
@ -1112,7 +1112,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57"
|
||||
integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==
|
||||
|
|
@ -1269,6 +1269,113 @@
|
|||
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016"
|
||||
integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==
|
||||
|
||||
"@emotion/babel-plugin@^11.11.0":
|
||||
version "11.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
||||
integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.16.7"
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/hash" "^0.9.1"
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
"@emotion/serialize" "^1.1.2"
|
||||
babel-plugin-macros "^3.1.0"
|
||||
convert-source-map "^1.5.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
find-root "^1.1.0"
|
||||
source-map "^0.5.7"
|
||||
stylis "4.2.0"
|
||||
|
||||
"@emotion/cache@^11.11.0":
|
||||
version "11.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff"
|
||||
integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
"@emotion/sheet" "^1.2.2"
|
||||
"@emotion/utils" "^1.2.1"
|
||||
"@emotion/weak-memoize" "^0.3.1"
|
||||
stylis "4.2.0"
|
||||
|
||||
"@emotion/hash@^0.9.1":
|
||||
version "0.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
|
||||
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
|
||||
|
||||
"@emotion/is-prop-valid@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337"
|
||||
integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
|
||||
"@emotion/memoize@^0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
|
||||
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
|
||||
|
||||
"@emotion/react@^11.11.4":
|
||||
version "11.11.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d"
|
||||
integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/babel-plugin" "^11.11.0"
|
||||
"@emotion/cache" "^11.11.0"
|
||||
"@emotion/serialize" "^1.1.3"
|
||||
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
|
||||
"@emotion/utils" "^1.2.1"
|
||||
"@emotion/weak-memoize" "^0.3.1"
|
||||
hoist-non-react-statics "^3.3.1"
|
||||
|
||||
"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451"
|
||||
integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==
|
||||
dependencies:
|
||||
"@emotion/hash" "^0.9.1"
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
"@emotion/unitless" "^0.8.1"
|
||||
"@emotion/utils" "^1.2.1"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@emotion/sheet@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec"
|
||||
integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
|
||||
|
||||
"@emotion/styled@^11.11.5":
|
||||
version "11.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb"
|
||||
integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.18.3"
|
||||
"@emotion/babel-plugin" "^11.11.0"
|
||||
"@emotion/is-prop-valid" "^1.2.2"
|
||||
"@emotion/serialize" "^1.1.4"
|
||||
"@emotion/use-insertion-effect-with-fallbacks" "^1.0.1"
|
||||
"@emotion/utils" "^1.2.1"
|
||||
|
||||
"@emotion/unitless@^0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3"
|
||||
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
|
||||
|
||||
"@emotion/use-insertion-effect-with-fallbacks@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963"
|
||||
integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==
|
||||
|
||||
"@emotion/utils@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4"
|
||||
integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==
|
||||
|
||||
"@emotion/weak-memoize@^0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6"
|
||||
integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
|
|
@ -1301,6 +1408,33 @@
|
|||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
|
||||
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
|
||||
|
||||
"@floating-ui/core@^1.0.0":
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1"
|
||||
integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==
|
||||
dependencies:
|
||||
"@floating-ui/utils" "^0.2.1"
|
||||
|
||||
"@floating-ui/dom@^1.6.1":
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef"
|
||||
integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.0.0"
|
||||
"@floating-ui/utils" "^0.2.0"
|
||||
|
||||
"@floating-ui/react-dom@^2.0.8":
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d"
|
||||
integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.6.1"
|
||||
|
||||
"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
|
||||
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.14":
|
||||
version "0.11.14"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
|
||||
|
|
@ -1629,6 +1763,110 @@
|
|||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
|
||||
integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==
|
||||
|
||||
"@mui/base@5.0.0-beta.40":
|
||||
version "5.0.0-beta.40"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2"
|
||||
integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@floating-ui/react-dom" "^2.0.8"
|
||||
"@mui/types" "^7.2.14"
|
||||
"@mui/utils" "^5.15.14"
|
||||
"@popperjs/core" "^2.11.8"
|
||||
clsx "^2.1.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/core-downloads-tracker@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046"
|
||||
integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==
|
||||
|
||||
"@mui/icons-material@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34"
|
||||
integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
|
||||
"@mui/lab@^5.0.0-alpha.169":
|
||||
version "5.0.0-alpha.169"
|
||||
resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.169.tgz#85b88b2f06ad78c586cde2b47970653e5fd895eb"
|
||||
integrity sha512-h6xe1K6ISKUbyxTDgdvql4qoDP6+q8ad5fg9nXQxGLUrIeT2jVrBuT/jRECSTufbnhzP+V5kulvYxaMfM8rEdA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/base" "5.0.0-beta.40"
|
||||
"@mui/system" "^5.15.14"
|
||||
"@mui/types" "^7.2.14"
|
||||
"@mui/utils" "^5.15.14"
|
||||
clsx "^2.1.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/material@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.14.tgz#a40bd5eccfa9fc925535e1f4d70c6cef77fa3a75"
|
||||
integrity sha512-kEbRw6fASdQ1SQ7LVdWR5OlWV3y7Y54ZxkLzd6LV5tmz+NpO3MJKZXSfgR0LHMP7meKsPiMm4AuzV0pXDpk/BQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/base" "5.0.0-beta.40"
|
||||
"@mui/core-downloads-tracker" "^5.15.14"
|
||||
"@mui/system" "^5.15.14"
|
||||
"@mui/types" "^7.2.14"
|
||||
"@mui/utils" "^5.15.14"
|
||||
"@types/react-transition-group" "^4.4.10"
|
||||
clsx "^2.1.0"
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
react-transition-group "^4.4.5"
|
||||
|
||||
"@mui/private-theming@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee"
|
||||
integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/utils" "^5.15.14"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/styled-engine@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2"
|
||||
integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@emotion/cache" "^11.11.0"
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/system@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.14.tgz#8a0c6571077eeb6b5f1ff7aa7ff6a3dc4a14200d"
|
||||
integrity sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/private-theming" "^5.15.14"
|
||||
"@mui/styled-engine" "^5.15.14"
|
||||
"@mui/types" "^7.2.14"
|
||||
"@mui/utils" "^5.15.14"
|
||||
clsx "^2.1.0"
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/types@^7.2.14":
|
||||
version "7.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9"
|
||||
integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==
|
||||
|
||||
"@mui/utils@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a"
|
||||
integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@types/prop-types" "^15.7.11"
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||
version "5.1.1-v1"
|
||||
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
|
||||
|
|
@ -1677,6 +1915,16 @@
|
|||
schema-utils "^3.0.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@popperjs/core@^2.11.8":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
|
||||
"@remix-run/router@1.15.3":
|
||||
version "1.15.3"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.3.tgz#d2509048d69dbb72d5389a14945339f1430b2d3c"
|
||||
integrity sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
|
||||
|
|
@ -2120,7 +2368,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
|
||||
integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
|
||||
|
||||
"@types/prop-types@*":
|
||||
"@types/prop-types@*", "@types/prop-types@^15.7.11":
|
||||
version "15.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
|
||||
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
|
||||
|
|
@ -2147,6 +2395,13 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@^4.4.10":
|
||||
version "4.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac"
|
||||
integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*":
|
||||
version "18.2.73"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.73.tgz#0579548ad122660d99e00499d22e33b81e73ed94"
|
||||
|
|
@ -2815,7 +3070,7 @@ at-least-node@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
autoprefixer@^10.4.13:
|
||||
autoprefixer@^10.4.13, autoprefixer@^10.4.19:
|
||||
version "10.4.19"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f"
|
||||
integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==
|
||||
|
|
@ -3258,6 +3513,11 @@ cliui@^7.0.2:
|
|||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clsx@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb"
|
||||
integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
|
|
@ -3400,7 +3660,7 @@ content-type@~1.0.4, content-type@~1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||
|
||||
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||
convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
|
||||
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
|
||||
|
|
@ -3668,7 +3928,7 @@ cssstyle@^2.3.0:
|
|||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^3.0.2:
|
||||
csstype@^3.0.2, csstype@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
|
|
@ -3912,6 +4172,14 @@ dom-converter@^0.2.0:
|
|||
dependencies:
|
||||
utila "~0.4"
|
||||
|
||||
dom-helpers@^5.0.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
|
||||
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.8.7"
|
||||
csstype "^3.0.2"
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
|
||||
|
|
@ -4723,6 +4991,11 @@ find-cache-dir@^3.3.1:
|
|||
make-dir "^3.0.2"
|
||||
pkg-dir "^4.1.0"
|
||||
|
||||
find-root@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
|
|
@ -5096,6 +5369,13 @@ he@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hoist-non-react-statics@^3.3.1:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||
dependencies:
|
||||
react-is "^16.7.0"
|
||||
|
||||
hoopy@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d"
|
||||
|
|
@ -7711,7 +7991,7 @@ postcss@^7.0.35:
|
|||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.3.5, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.4:
|
||||
postcss@^8.3.5, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.38, postcss@^8.4.4:
|
||||
version "8.4.38"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||
|
|
@ -7791,7 +8071,7 @@ prompts@^2.0.1, prompts@^2.4.2:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.5"
|
||||
|
||||
prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
|
@ -7924,7 +8204,7 @@ react-error-overlay@^6.0.11:
|
|||
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
|
||||
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
|
||||
|
||||
react-is@^16.13.1:
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
|
@ -7934,7 +8214,7 @@ react-is@^17.0.1:
|
|||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^18.0.0:
|
||||
react-is@^18.0.0, react-is@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
|
@ -7944,6 +8224,21 @@ react-refresh@^0.11.0:
|
|||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
|
||||
|
||||
react-router-dom@^6.22.3:
|
||||
version "6.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.3.tgz#9781415667fd1361a475146c5826d9f16752a691"
|
||||
integrity sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.15.3"
|
||||
react-router "6.22.3"
|
||||
|
||||
react-router@6.22.3:
|
||||
version "6.22.3"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.3.tgz#9d9142f35e08be08c736a2082db5f0c9540a885e"
|
||||
integrity sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==
|
||||
dependencies:
|
||||
"@remix-run/router" "1.15.3"
|
||||
|
||||
react-scripts@5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"
|
||||
|
|
@ -7999,6 +8294,16 @@ react-scripts@5.0.1:
|
|||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
react-transition-group@^4.4.5:
|
||||
version "4.4.5"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
|
||||
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
dom-helpers "^5.0.1"
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
|
|
@ -8565,6 +8870,11 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
|
||||
|
||||
source-map@^0.7.3:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
|
||||
|
|
@ -8818,6 +9128,11 @@ stylehacks@^5.1.1:
|
|||
browserslist "^4.21.4"
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
stylis@4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
|
||||
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
|
||||
|
||||
sucrase@^3.32.0:
|
||||
version "3.35.0"
|
||||
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
|
||||
|
|
@ -8907,7 +9222,7 @@ symbol-tree@^3.2.4:
|
|||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
|
||||
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
|
||||
|
||||
tailwindcss@^3.0.2:
|
||||
tailwindcss@^3.0.2, tailwindcss@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.3.tgz#be48f5283df77dfced705451319a5dffb8621519"
|
||||
integrity sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==
|
||||
|
|
|
|||