Removed legacy react frontend
This commit is contained in:
parent
5b8d1cbdb1
commit
e95de87b03
18 changed files with 0 additions and 4218 deletions
|
@ -1,19 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: { browser: true, es2020: true },
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:react-hooks/recommended',
|
|
||||||
],
|
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
plugins: ['react-refresh'],
|
|
||||||
rules: {
|
|
||||||
'react-refresh/only-export-components': [
|
|
||||||
'warn',
|
|
||||||
{ allowConstantExport: true },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", { "allowTypedFunctionExpressions": false }],
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
|
||||||
<link rel="shortcut icon"
|
|
||||||
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-1756clo' focusable='false' aria-hidden='true' viewBox='0 0 24 24' data-testid='AcUnitIcon'%3E%3Cpath d='M22 11h-4.17l3.24-3.24-1.41-1.42L15 11h-2V9l4.66-4.66-1.42-1.41L13 6.17V2h-2v4.17L7.76 2.93 6.34 4.34 11 9v2H9L4.34 6.34 2.93 7.76 6.17 11H2v2h4.17l-3.24 3.24 1.41 1.42L9 13h2v2l-4.66 4.66 1.42 1.41L11 17.83V22h2v-4.17l3.24 3.24 1.42-1.41L13 15v-2h2l4.66 4.66 1.41-1.42L17.83 13H22z'%3E%3C/path%3E%3C/svg%3E" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>FrostByte</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
3448
client/package-lock.json
generated
3448
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,35 +0,0 @@
|
||||||
{
|
|
||||||
"name": "mui-practice",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite --host",
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"clean": "rm -r node_modules"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@emotion/react": "^11.11.1",
|
|
||||||
"@emotion/styled": "^11.11.0",
|
|
||||||
"@mui/icons-material": "^5.14.11",
|
|
||||||
"@mui/material": "^5.14.11",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^18.2.15",
|
|
||||||
"@types/react-dom": "^18.2.7",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
|
||||||
"eslint": "^8.45.0",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
|
||||||
"eslint-plugin-react-refresh": "^0.4.3",
|
|
||||||
"sass": "^1.68.0",
|
|
||||||
"typescript": "^5.0.2",
|
|
||||||
"vite": "^4.4.5",
|
|
||||||
"vite-plugin-qrcode": "^0.2.2"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
import Box from '@mui/material/Box'
|
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
import Header from './Header.tsx'
|
|
||||||
import Primary from './Primary.tsx';
|
|
||||||
import Footer from './Footer.tsx';
|
|
||||||
import LoginDialog from './LoginDialog.tsx';
|
|
||||||
import { LoginContext } from './Context.tsx';
|
|
||||||
|
|
||||||
|
|
||||||
// JSX.Element is the return type of every React component
|
|
||||||
function App(): JSX.Element {
|
|
||||||
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
|
||||||
const [currentUser, setCurrentUser] = useState<string | undefined>(undefined);
|
|
||||||
const [userToken, setUserToken] = useState<string | undefined>(undefined);
|
|
||||||
|
|
||||||
const loginContextData = {
|
|
||||||
loginModalOpen: loginModalOpen,
|
|
||||||
currentUser: currentUser,
|
|
||||||
userToken: userToken,
|
|
||||||
setOpen: setLoginModalOpen,
|
|
||||||
setCurrentUser: setCurrentUser,
|
|
||||||
setUserToken: setUserToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
// const loginContextData = { open, setOpen };
|
|
||||||
// const loginContext = createContext(loginContextData);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LoginContext.Provider value={loginContextData} >
|
|
||||||
<Box flexDirection={"column"} display={"flex"} sx={{ width: "100%", minHeight: "100vh", backgroundColor:"background.default"}}>
|
|
||||||
<Header />
|
|
||||||
<LoginDialog />
|
|
||||||
<Primary />
|
|
||||||
<Footer />
|
|
||||||
</Box>
|
|
||||||
</LoginContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { createContext } from "react"
|
|
||||||
|
|
||||||
export const TestContext = createContext("Test123")
|
|
||||||
|
|
||||||
interface LoginCTX {
|
|
||||||
loginModalOpen: boolean;
|
|
||||||
currentUser?: string;
|
|
||||||
userToken?: string;
|
|
||||||
setOpen?: (open: boolean) => void;
|
|
||||||
setCurrentUser?: (username: string) => void;
|
|
||||||
setUserToken?: (token: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginContextData = {
|
|
||||||
loginModalOpen: false,
|
|
||||||
currentUser: undefined,
|
|
||||||
userToken: undefined,
|
|
||||||
setOpen: undefined,
|
|
||||||
setCurrentUser: undefined,
|
|
||||||
setUserToken: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LoginContext = createContext<LoginCTX>(loginContextData);
|
|
|
@ -1,36 +0,0 @@
|
||||||
// import React from "react";
|
|
||||||
import Typography from "@mui/material/Typography";
|
|
||||||
import Container from "@mui/material/Container";
|
|
||||||
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import { Grid } from "@mui/material";
|
|
||||||
|
|
||||||
function Footer(): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Box sx={{
|
|
||||||
flexGrow: "1",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
backgroundColor: "#242424",
|
|
||||||
// backgroundColor: "background.paper",
|
|
||||||
color: "text.secondary"
|
|
||||||
}}>
|
|
||||||
<Grid container sx={{ mt: 6, maxWidth: "1000px", width: "100%", mx: "auto" }}>
|
|
||||||
<Container sx={{textAlign: "center"}}>
|
|
||||||
<Typography color="text.secondary" gutterBottom sx={{userSelect: "none",opacity:"40%"}}>
|
|
||||||
Δ DeltaLabs {new Date().getFullYear()}
|
|
||||||
</Typography>
|
|
||||||
</Container>
|
|
||||||
{/* { ["Tasteful", "Looking", "Footer"].map((letter): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<Grid key={letter} item xs={12} sm={6} md={4} lg={4} sx={{textAlign: "center"}}>
|
|
||||||
<Typography>{letter}</Typography>
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
}) } */}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Footer;
|
|
|
@ -1,172 +0,0 @@
|
||||||
import {
|
|
||||||
AppBar,
|
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Grid,
|
|
||||||
Link,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import Box from "@mui/material/Box";
|
|
||||||
import AcUnitIcon from "@mui/icons-material/AcUnit";
|
|
||||||
import { cyan } from "@mui/material/colors";
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { LoginContext } from "./Context";
|
|
||||||
import AccountCircleIcon from "@mui/icons-material/AccountCircle";
|
|
||||||
import HomeIcon from "@mui/icons-material/Home";
|
|
||||||
import PostAddIcon from "@mui/icons-material/PostAdd";
|
|
||||||
|
|
||||||
function LoginDisplay({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
|
||||||
const loginCtx = useContext(LoginContext);
|
|
||||||
|
|
||||||
const handleLogin = (): void => {
|
|
||||||
console.log("Login button pressed");
|
|
||||||
if (loginCtx.currentUser == undefined) {
|
|
||||||
loginCtx.setOpen?.(true); // If the loginCtx.setOpen is defined, call it with true as the argument
|
|
||||||
} else {
|
|
||||||
loginCtx.setCurrentUser?.("");
|
|
||||||
loginCtx.setUserToken?.("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loginCtx.currentUser != undefined) {
|
|
||||||
return (
|
|
||||||
<Box sx={{ textAlign: "right", ...sx }}>
|
|
||||||
<Typography color={"#808080"} sx={{ textAlign: "right" }}>
|
|
||||||
Logged in as:
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ textAlign: "right" }}>
|
|
||||||
<Link href="#" underline="hover" onClick={(): void => handleLogin()}>
|
|
||||||
{loginCtx.currentUser}
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ textAlign: "right", ...sx }}>
|
|
||||||
<Button
|
|
||||||
variant="text"
|
|
||||||
startIcon={<AccountCircleIcon />}
|
|
||||||
onClick={(): void => handleLogin()}
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Header({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
|
||||||
return (
|
|
||||||
<AppBar
|
|
||||||
position="static"
|
|
||||||
sx={{
|
|
||||||
p: 1,
|
|
||||||
px: 3,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
px={2}
|
|
||||||
spacing={2}
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
display: "flex",
|
|
||||||
maxWidth: "1200px",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={4}>
|
|
||||||
<HeaderLogo clickable />
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={4}>
|
|
||||||
<NavButtons />
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={6} md={4} lg={4}>
|
|
||||||
<LoginDisplay />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</AppBar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function HeaderLogo({
|
|
||||||
clickable,
|
|
||||||
sx,
|
|
||||||
}: {
|
|
||||||
clickable?: boolean;
|
|
||||||
sx?: React.CSSProperties;
|
|
||||||
}): JSX.Element {
|
|
||||||
const clickStyle = {
|
|
||||||
transition: "0.3s all ease-in-out",
|
|
||||||
":hover": { textShadow: "0 0px 15px" + cyan[400] },
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: "fit-content",
|
|
||||||
// height: "fit-content",
|
|
||||||
// display: "flex",
|
|
||||||
// flexDirection: "row",
|
|
||||||
// alignItems: "center",
|
|
||||||
// justifyContent: "center",
|
|
||||||
userSelect: "none",
|
|
||||||
cursor: "pointer",
|
|
||||||
...(clickable ? clickStyle : {}),
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
fontSize={"2rem"}
|
|
||||||
sx={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
|
||||||
>
|
|
||||||
<AcUnitIcon
|
|
||||||
fontSize="inherit"
|
|
||||||
sx={{ height: "100%", color: cyan[400], mr: 1, mb: 1.1 }}
|
|
||||||
></AcUnitIcon>
|
|
||||||
FrostByte
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavButtons({ sx }: { sx?: React.CSSProperties }): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
maxWidth: "400px",
|
|
||||||
width: 1,
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ButtonGroup
|
|
||||||
variant="text"
|
|
||||||
aria-label="text button group"
|
|
||||||
sx={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{["Home", "New"].map((typename): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
startIcon={typename === "Home" ? <HomeIcon /> : <PostAddIcon />}
|
|
||||||
key={typename}
|
|
||||||
sx={{ px: 2, bgcolor: "#FFFFFF15", width: 1 }}
|
|
||||||
>
|
|
||||||
{typename}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ButtonGroup>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header;
|
|
|
@ -1,132 +0,0 @@
|
||||||
import { Dialog } from "@mui/material";
|
|
||||||
import { DialogTitle } from "@mui/material";
|
|
||||||
import { DialogContent } from "@mui/material";
|
|
||||||
import { DialogContentText } from "@mui/material";
|
|
||||||
import { DialogActions } from "@mui/material";
|
|
||||||
import { Button } from "@mui/material";
|
|
||||||
import { TextField } from "@mui/material";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useContext } from "react";
|
|
||||||
import { LoginContext } from "./Context";
|
|
||||||
// import { TestContext } from "./Context";
|
|
||||||
|
|
||||||
interface RegisterData {
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
captcha: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoginData {
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendRegister(data: RegisterData): void {
|
|
||||||
console.log(JSON.stringify(data));
|
|
||||||
fetch("/api/register", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoginResponse {
|
|
||||||
username: string,
|
|
||||||
token: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendLogin(data: LoginData): Promise<LoginResponse> {
|
|
||||||
// console.log(JSON.stringify(data));
|
|
||||||
const response_promise = await fetch("/api/login", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
|
|
||||||
const logindata = await response_promise.json();
|
|
||||||
return (logindata as LoginResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
function LoginDialog(): JSX.Element {
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
|
|
||||||
const loginCTX = useContext(LoginContext);
|
|
||||||
|
|
||||||
const setLoggedInAs = (username: string, token: string): void => {
|
|
||||||
loginCTX.setCurrentUser?.(username);
|
|
||||||
loginCTX.setUserToken?.(token);
|
|
||||||
handleClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// const setLogOut = (): void => {
|
|
||||||
// loginCTX.setCurrentUser?.(undefined);
|
|
||||||
// loginCTX.setUserToken?.(undefined);
|
|
||||||
// localStorage.removeItem("loginState");
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
loginCTX.setOpen?.(false);
|
|
||||||
setUsername("");
|
|
||||||
setPassword("");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check for localStorage login state
|
|
||||||
useEffect((): void => {
|
|
||||||
const loginState = JSON.parse(localStorage.getItem("loginState")||"{}");
|
|
||||||
if (loginState.username && loginState.token) {
|
|
||||||
setLoggedInAs(loginState.username, loginState.token);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLogin = async (): Promise<void> => {
|
|
||||||
const response = await sendLogin({ username: username, password: password });
|
|
||||||
if (response && response.username && response.token) {
|
|
||||||
setLoggedInAs(response.username, response.token);
|
|
||||||
localStorage.setItem("loginState", JSON.stringify(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRegister = (): void => {
|
|
||||||
sendRegister({ username: username, password: password, captcha: "test" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={loginCTX.loginModalOpen} onClose={handleClose} sx={{ p: 2, top: "-40%" }}>
|
|
||||||
<DialogTitle>Login</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>
|
|
||||||
Please enter your username and password.
|
|
||||||
</DialogContentText>
|
|
||||||
<TextField
|
|
||||||
autoFocus
|
|
||||||
margin="dense"
|
|
||||||
id="username"
|
|
||||||
label="Username"
|
|
||||||
type="text"
|
|
||||||
fullWidth
|
|
||||||
value={username}
|
|
||||||
onChange={(e): void => setUsername(e.target.value)}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
margin="dense"
|
|
||||||
id="password"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
fullWidth
|
|
||||||
value={password}
|
|
||||||
onChange={(e): void => setPassword(e.target.value)}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={handleLogin}>Login</Button>
|
|
||||||
</DialogActions>
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={handleRegister}>Register</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoginDialog;
|
|
|
@ -1,100 +0,0 @@
|
||||||
import { Card, CardActions, CardContent, IconButton, Typography } from "@mui/material";
|
|
||||||
import { ArrowDownward, ArrowUpward, Comment, Share } from "@mui/icons-material";
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export interface Post {
|
|
||||||
uuid: string,
|
|
||||||
title: string,
|
|
||||||
content: string,
|
|
||||||
votes: { up: number, down: number },
|
|
||||||
}
|
|
||||||
|
|
||||||
// const URL = "http://localhost:8080/api/";
|
|
||||||
|
|
||||||
function sendVote(post: Post, direction: string): void {
|
|
||||||
fetch('/api/vote/' + post.uuid + '/' + direction, { method: 'POST' });
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VoteDirection { UP = 1, DOWN = -1, NONE = 0 }
|
|
||||||
|
|
||||||
// // Single post
|
|
||||||
export function PostCard({ post }: { post: Post }): JSX.Element {
|
|
||||||
// const [myVote, setMyVote] = useState({ up: 0, down: 0 });
|
|
||||||
const [myVote, setMyVote] = useState(VoteDirection.NONE);
|
|
||||||
const [voteCount, setVoteCount] = useState({
|
|
||||||
upvotes: post.votes.up,
|
|
||||||
downvotes: post.votes.down
|
|
||||||
});
|
|
||||||
setVoteCount; // To silence the linter, peak coding right here
|
|
||||||
|
|
||||||
// Atrocious code
|
|
||||||
const votePress = (vote: VoteDirection): void => {
|
|
||||||
console.log(vote);
|
|
||||||
if (vote === VoteDirection.UP) {
|
|
||||||
if (myVote === VoteDirection.NONE) { // Upvote
|
|
||||||
sendVote(post, 'up');
|
|
||||||
setMyVote(VoteDirection.UP);
|
|
||||||
}
|
|
||||||
else if (myVote === VoteDirection.UP) { // Unvote
|
|
||||||
sendVote(post, 'unupvote');
|
|
||||||
setMyVote(VoteDirection.NONE);
|
|
||||||
}
|
|
||||||
else if (myVote === VoteDirection.DOWN) { // Change vote
|
|
||||||
sendVote(post, 'undownvote');
|
|
||||||
sendVote(post, 'up');
|
|
||||||
setMyVote(VoteDirection.UP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vote === VoteDirection.DOWN) {
|
|
||||||
if (myVote === VoteDirection.NONE) { // Downvote
|
|
||||||
sendVote(post, 'down');
|
|
||||||
setMyVote(VoteDirection.DOWN);
|
|
||||||
}
|
|
||||||
else if (myVote === VoteDirection.DOWN) { // Unvote
|
|
||||||
sendVote(post, 'undownvote');
|
|
||||||
setMyVote(VoteDirection.NONE);
|
|
||||||
}
|
|
||||||
else if (myVote === VoteDirection.UP) { // Change vote
|
|
||||||
sendVote(post, 'unupvote');
|
|
||||||
sendVote(post, 'down');
|
|
||||||
setMyVote(VoteDirection.DOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card key={post.uuid} sx={{ width: 1, bgcolor: "background.default" }}>
|
|
||||||
<CardContent>
|
|
||||||
<Typography variant="h6">
|
|
||||||
{post.title}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography sx={{ overflowWrap: 'break-word' }} variant="body2">
|
|
||||||
{post.content ? post.content : "No content"}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
</CardContent>
|
|
||||||
|
|
||||||
<CardActions>
|
|
||||||
<IconButton size='small' color='primary' aria-label="Comment"><Comment /></IconButton>
|
|
||||||
|
|
||||||
<IconButton color={myVote > 0 ? 'success' : 'default'}
|
|
||||||
onClick={(): void => votePress(VoteDirection.UP)} size='small' aria-label="Upvote"><ArrowUpward />
|
|
||||||
{voteCount.upvotes + Math.max(myVote, 0)}
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<IconButton color={myVote < 0 ? 'secondary' : 'default'}
|
|
||||||
onClick={(): void => votePress(VoteDirection.DOWN)} size='small' aria-label="Downvote"><ArrowDownward />
|
|
||||||
{voteCount.downvotes + Math.max(-myVote, 0)}
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<div style={{ marginLeft: 'auto' }}>
|
|
||||||
<IconButton size='small' aria-label="Share"><Share /></IconButton>
|
|
||||||
</div>
|
|
||||||
</CardActions>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
import { Box, Container, Grid, Button, Paper, TextField, FormControl } from "@mui/material";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import SendIcon from '@mui/icons-material/Send';
|
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
|
||||||
import { Post, PostCard } from "./Post";
|
|
||||||
|
|
||||||
function Primary(): JSX.Element {
|
|
||||||
const [posts, setPosts] = useState<Post[]>([]);
|
|
||||||
|
|
||||||
const refreshPosts = (): void => {
|
|
||||||
fetch("/api/").then((response): void => {
|
|
||||||
response.json().then((data): void => {
|
|
||||||
setPosts(data);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect((): void => {
|
|
||||||
refreshPosts();
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container sx={{ p: 2, display: "flex", flexDirection: "column", alignItems: "center", backgroundColor: "background.default", minHeight: "60vh" }}>
|
|
||||||
<PostInput newPostCallback={refreshPosts} />
|
|
||||||
<PostContainer posts={posts} />
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PostContainer({ posts }: { posts: Post[] }): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Grid container justifyContent={"center"} spacing={2} pb={2}>
|
|
||||||
{posts.map((p): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<Grid item xs={12} sm={12} md={6} lg={6} xl={6} key={p.uuid}>
|
|
||||||
<PostCard key={p.uuid} post={p} />
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</Grid>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewPost {
|
|
||||||
content: string,
|
|
||||||
token: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
function PostInput({ newPostCallback }: { newPostCallback: () => void }): JSX.Element {
|
|
||||||
const [currentInput, setCurrentInput] = useState("");
|
|
||||||
|
|
||||||
const handleSubmit = (): void => {
|
|
||||||
if (currentInput) submitPostToServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitPostToServer = async (): Promise<void> => {
|
|
||||||
const newPost: NewPost = { content: currentInput, token: "" }
|
|
||||||
|
|
||||||
const response = await fetch("/api/", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(newPost)
|
|
||||||
})
|
|
||||||
const data = await response.json();
|
|
||||||
console.log(data);
|
|
||||||
newPostCallback();
|
|
||||||
setCurrentInput("");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box py={2} display={"flex"} flexDirection={"column"} width={1}>
|
|
||||||
<Paper sx={{ p: 2, display: "flex", flexDirection: "column", alignContent: "center", alignItems: "center" }} elevation={4}>
|
|
||||||
<FormControl fullWidth>
|
|
||||||
{/* <TextField sx={{pb:1}} size="small" id="outlined-basic" label="Title" variant="outlined" value={title} fullWidth onChange={(a): void => setTitle(a.target.value)} /> */}
|
|
||||||
<TextField multiline id="outlined-basic" label="Share your thoughts" variant="outlined" minRows={4} value={currentInput} fullWidth onChange={(a): void => setCurrentInput(a.target.value)} />
|
|
||||||
<Container disableGutters sx={{ mt:1, display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "center", width: "100%" }}>
|
|
||||||
<Button sx={{mr:1}} type="reset" startIcon={<CancelIcon/>} variant="outlined" onClick={handleSubmit}>Cancel</Button>
|
|
||||||
<Button type="submit" startIcon={<SendIcon />} variant="contained" onClick={handleSubmit}>Send</Button>
|
|
||||||
</Container>
|
|
||||||
</FormControl>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Primary;
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { createTheme } from "@mui/material";
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
palette: {
|
|
||||||
mode: "dark",
|
|
||||||
// primary: {
|
|
||||||
// main: "#ff0000",
|
|
||||||
// },
|
|
||||||
// secondary: {
|
|
||||||
// main: "#00ff00",
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
typography: {
|
|
||||||
fontFamily: "Roboto, sans-serif",
|
|
||||||
// h1: {
|
|
||||||
// fontSize: "3.5rem",
|
|
||||||
// },
|
|
||||||
// h3: {
|
|
||||||
// fontSize: "2rem",
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default theme;
|
|
|
@ -1,22 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
|
||||||
import App from './App.tsx'
|
|
||||||
import './style/index.scss'
|
|
||||||
|
|
||||||
import theme from './Theme.tsx'
|
|
||||||
import { ThemeProvider } from '@mui/material'
|
|
||||||
import { CssBaseline } from '@mui/material'
|
|
||||||
|
|
||||||
import { TestContext } from './Context.tsx'
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<CssBaseline />
|
|
||||||
<ThemeProvider theme={theme}>
|
|
||||||
<TestContext.Provider value="Test123">
|
|
||||||
<App />
|
|
||||||
</TestContext.Provider>
|
|
||||||
</ThemeProvider>
|
|
||||||
<CssBaseline />
|
|
||||||
</React.StrictMode>,
|
|
||||||
)
|
|
|
@ -1 +0,0 @@
|
||||||
// No free standing CSS
|
|
1
client/src/vite-env.d.ts
vendored
1
client/src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
// / <reference types="vite/client" />
|
|
|
@ -1,25 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import react from '@vitejs/plugin-react-swc'
|
|
||||||
import { qrcode } from 'vite-plugin-qrcode'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
build: {
|
|
||||||
// outDir: '../server/public' // Override default outDir('dist')
|
|
||||||
},
|
|
||||||
plugins: [react(), qrcode()],
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
open: true,
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://localhost:8080/api',
|
|
||||||
changeOrigin: true,
|
|
||||||
secure: false,
|
|
||||||
rewrite: (path): string => path.replace(/^\/api/, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
Loading…
Reference in a new issue