Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completed most basic admin functionality #34

Merged
merged 14 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions client/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'react', 'unused-imports'],
settings: {
react: {
version: 'detect',
},
},
rules: {
'react/react-in-jsx-scope': 'off',
"unused-imports/no-unused-imports": "error",
},
};
12 changes: 9 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,37 @@
"name": "client",
"version": "0.1.0",
"private": true,
"main": "index.tsx",
"license": "MIT",
"dependencies": {
"@tailwindcss/forms": "^0.5.3",
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@tailwindcss/forms": "^0.5.3",
"localforage": "^1.10.0",
"match-sorter": "^6.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.44.3",
"react-router-dom": "^6.8.1",
"react-scripts": "5.0.1",
"tailwind-merge": "^1.9.1",
"typescript": "^4.4.2",
"universal-cookie": "^4.0.4"
"universal-cookie": "^4.0.4",
"zustand": "^4.3.8"
},
"devDependencies": {
"esbuild": "^0.17.8",
"eslint-plugin-unused-imports": "^2.0.0",
"tailwindcss": "^3.2.6"
},
"scripts": {
"docker": "yarn install && yarn start",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx"
},
"eslintConfig": {
"extends": [
Expand Down
4 changes: 2 additions & 2 deletions client/src/admin/AddJudges.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import UploadCSVForm from '../components/admin/add-judges/UploadCSVForm';
const AddJudges = () => {
return (
<>
<JuryHeader withLogout isAdmin />
<JuryHeader withBack withLogout isAdmin />
<div className="flex flex-col items-start justify-center w-full px-8 py-4 md:px-16 md:py-8">
<h1 className="text-4xl font-bold">Add Judges</h1>
<AddJudgeStatsPanel />
<div className="mt-8 flex flex-col w-full space-y-8">
<NewJudgeForm />
<UploadCSVForm />
<UploadCSVForm format="judge" />
</div>
</div>
</>
Expand Down
20 changes: 17 additions & 3 deletions client/src/admin/AddProjects.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import JuryHeader from "../components/JuryHeader";
import UploadCSVForm from "../components/admin/add-judges/UploadCSVForm";
import AddProjectsStatsPanel from "../components/admin/add-projects/AddProjectsStatsPanel";
import NewProjectForm from "../components/admin/add-projects/NewProjectForm";

const AddProjects = () => {
return (
<div>
<h1>Add Projects</h1>
</div>
<>
<JuryHeader withBack withLogout isAdmin />
<div className="flex flex-col items-start justify-center w-full px-8 py-4 md:px-16 md:py-8">
<h1 className="text-4xl font-bold">Add Projects</h1>
<AddProjectsStatsPanel />
<div className="mt-8 flex flex-col w-full space-y-8">
<NewProjectForm />
<UploadCSVForm format="project" />
<UploadCSVForm format="devpost" />
</div>
</div>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion client/src/admin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState } from 'react';
import AdminStatsPanel from '../components/admin/AdminStatsPanel';
import AdminTable from '../components/admin/AdminTable';
import AdminTable from '../components/admin/tables/AdminTable';
import AdminToggleSwitch from '../components/admin/AdminToggleSwitch';
import AdminToolbar from '../components/admin/AdminToolbar';
import JuryHeader from '../components/JuryHeader';
Expand Down
7 changes: 4 additions & 3 deletions client/src/admin/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Button from '../components/Button';
import Container from '../components/Container';
import JuryHeader from '../components/JuryHeader';
import LoginBlock from '../components/LoginBlock';
import TextInput from '../components/TextInput';
import PasswordInput from '../components/PasswordInput';

const AdminLogin = () => {
const [password, setPassword] = useState('');
Expand Down Expand Up @@ -87,15 +87,16 @@ const AdminLogin = () => {
<>
<JuryHeader />
<Container>
<TextInput
<PasswordInput
label="Enter the admin password"
placeholder="Admin password..."
onKeyPress={handleEnter}
onChange={handleChange}
error={error}
setError={setError}
errorMessage="Invalid admin password"
isPassword
isHidden
className='w-4/5'
/>
<div className="my-12" />
<Button type="primary" onClick={login}>
Expand Down
22 changes: 16 additions & 6 deletions client/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ interface ButtonProps {
href?: string;

/* If href is not defined, function should be written to handle button click */
onClick?: (e: React.MouseEvent<any>) => void;
onClick?: (e: React.MouseEvent<Element>) => void;

/* Type of the button */
type: 'primary' | 'outline' | 'text';
type: 'primary' | 'outline' | 'text' | 'error';

/* If true, sets button as a disabled button */
disabled?: boolean;
Expand All @@ -34,17 +34,27 @@ interface ButtonProps {
*/
const Button = (props: ButtonProps) => {
// Define formatting
const defaultFormat =
'py-4 w-3/4 text-center text-2xl no-underline outline-none md:w-2/3 ';
const defaultFormat = 'py-4 w-3/4 text-center text-2xl no-underline outline-none md:w-2/3 ';
const borderFormat = props.type === 'outline' ? 'border-lightest border-[3px]' : 'border-none';
const typeFormat =
props.type === 'primary' ? 'bg-primary text-background' : 'bg-transparent text-primary';
props.type === 'primary'
? 'bg-primary text-background'
: props.type === 'error'
? 'bg-error text-background'
: 'bg-transparent text-primary';
const squareFormat = props.square ? 'rounded-lg' : 'rounded-full';
const varFormat = !props.disabled
? typeFormat + ' cursor-pointer duration-200 hover:scale-110 focus:scale-110'
: 'cursor-auto text-lighter bg-backgroundDark';
const boldFormat = props.bold ? 'font-bold' : 'font-normal';
const formatting = twMerge(defaultFormat, borderFormat, varFormat, squareFormat, boldFormat, props.className);
const formatting = twMerge(
defaultFormat,
borderFormat,
varFormat,
squareFormat,
boldFormat,
props.className
);

// Disable button bc links cannot be disabled
return props.disabled ? (
Expand Down
23 changes: 22 additions & 1 deletion client/src/components/JuryHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ import { twMerge } from 'tailwind-merge';
import Cookies from 'universal-cookie';
import logoutButton from '../assets/logout.svg';

const JuryHeader = (props: { withLogout?: boolean; isAdmin?: boolean }) => {
interface JuryHeaderProps {
/* Whether to show the back to admin button */
withBack?: boolean;

/* Whether to show the logout button */
withLogout?: boolean;

/* Whether the user is an admin */
isAdmin?: boolean;
}

const JuryHeader = (props: JuryHeaderProps) => {
const navigate = useNavigate();
const cookies = new Cookies();

Expand All @@ -13,6 +24,8 @@ const JuryHeader = (props: { withLogout?: boolean; isAdmin?: boolean }) => {
navigate('/');
};

const backToAdmin = () => navigate('/admin');

const adminCenter = props.isAdmin ? 'text-center' : '';

return (
Expand Down Expand Up @@ -41,6 +54,14 @@ const JuryHeader = (props: { withLogout?: boolean; isAdmin?: boolean }) => {
>
{process.env.REACT_APP_JURY_NAME}
</div>
{props.withBack && (
<div
className="absolute top-0 left-6 flex items-center cursor-pointer border-none bg-transparent hover:scale-110 duration-200 text-light text-xl mr-2"
onClick={backToAdmin}
>
◂&nbsp;&nbsp;Back
</div>
)}
{props.withLogout && (
<div
className="absolute top-0 right-6 flex items-center cursor-pointer border-none bg-transparent hover:scale-110 duration-200"
Expand Down
82 changes: 82 additions & 0 deletions client/src/components/PasswordInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';

interface PasswordInputProps {
/* Set true if text input has errored */
error?: boolean;

/* Setter for error state; will reset if no longer errored */
setError?: React.Dispatch<React.SetStateAction<boolean>>;

/* Error message to show if text incorrect */
errorMessage?: string;

/* Max Length of text field; null for no max */
maxLength?: number;

/* Placeholder of text field */
placeholder?: string;

/* Label under the field */
label: string;

/* Classname styling */
className?: string;

/* Handler for text input onChange */
onChange?: React.ChangeEventHandler<HTMLInputElement>;

/* Handler for text input onKeyPress */
onKeyPress?: React.KeyboardEventHandler<HTMLInputElement>;

/* Set default value of input field */
value?: string;

/* True if it's a password field */
isHidden?: boolean;
}

const PasswordInput = (props: PasswordInputProps) => {
const [focused, setFocused] = useState(false);

const handleChange = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (props.onKeyPress) props.onKeyPress(e);
if (props.setError) props.setError(false);
};

return (
<>
<input
type={props.isHidden ? 'password' : 'text'}
maxLength={props.maxLength}
id="text-input"
placeholder={props.placeholder}
className={twMerge(
'block text-4xl font-mono font-normal text-black bg-transparent outline-none border-0 border-b-[3px] border-solid p-0 duration-200',
props.error ? 'border-error ' : 'border-light focus:border-primary ',
props.className
)}
onFocus={() => {
setFocused(true);
}}
onBlur={() => {
setFocused(false);
}}
onChange={props.onChange}
onKeyDown={handleChange}
/>
<label
htmlFor="text-input"
id="text-input-label"
className={twMerge(
'mt-4 text-2xl duration-200',
props.error ? 'text-error' : focused ? 'text-primary' : ''
)}
>
{props.error ? (props.errorMessage || props.label) : props.label}
</label>
</>
);
};

export default PasswordInput;
25 changes: 25 additions & 0 deletions client/src/components/TextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { UseFormRegister } from 'react-hook-form';

interface TextAreaProps {
/* Name of the field */
name: string;

/* Placeholder of the field */
placeholder: string;

/* Register function from react-hook-form */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
register: UseFormRegister<any>;
}

const TextArea = (props: TextAreaProps) => {
return (
<textarea
className="w-full h-36 px-4 py-4 text-2xl border-lightest border-2 rounded-sm focus:border-primary focus:border-4 focus:outline-none"
placeholder={props.placeholder}
{...props.register(props.name)}
/>
);
};

export default TextArea;
Loading