From 691aa744a0398f185b3ca98a36fbd83806c7786c Mon Sep 17 00:00:00 2001 From: Parker Date: Sun, 10 Nov 2024 16:36:16 -0600 Subject: TOO MUCH STUFF --- app/src/App.tsx | 19 +++++++ app/src/assets/react.svg | 1 + app/src/components/Dashboard.tsx | 47 ++++++++++++++++ app/src/components/Login.tsx | 84 ++++++++++++++++++++++++++++ app/src/components/Signup.tsx | 106 ++++++++++++++++++++++++++++++++++++ app/src/helpers/api.js | 27 +++++++++ app/src/index.css | 68 +++++++++++++++++++++++ app/src/main.tsx | 10 ++++ app/src/styles/Dashboard.module.css | 74 +++++++++++++++++++++++++ app/src/styles/Login.module.css | 87 +++++++++++++++++++++++++++++ app/src/vite-env.d.ts | 1 + 11 files changed, 524 insertions(+) create mode 100644 app/src/App.tsx create mode 100644 app/src/assets/react.svg create mode 100644 app/src/components/Dashboard.tsx create mode 100644 app/src/components/Login.tsx create mode 100644 app/src/components/Signup.tsx create mode 100644 app/src/helpers/api.js create mode 100644 app/src/index.css create mode 100644 app/src/main.tsx create mode 100644 app/src/styles/Dashboard.module.css create mode 100644 app/src/styles/Login.module.css create mode 100644 app/src/vite-env.d.ts (limited to 'app/src') diff --git a/app/src/App.tsx b/app/src/App.tsx new file mode 100644 index 0000000..75cd203 --- /dev/null +++ b/app/src/App.tsx @@ -0,0 +1,19 @@ +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +// Import components +import Login from './components/Login' +import Signup from './components/Signup' +import Dashboard from './components/Dashboard' + +function App() { + return ( + + + } /> + } /> + } /> + + + ) +} + +export default App diff --git a/app/src/assets/react.svg b/app/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/app/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/components/Dashboard.tsx b/app/src/components/Dashboard.tsx new file mode 100644 index 0000000..bcab092 --- /dev/null +++ b/app/src/components/Dashboard.tsx @@ -0,0 +1,47 @@ +import { useState, useEffect } from 'react'; +import Axios from 'axios'; +import styles from '../styles/Dashboard.module.css'; +// import { accessAPI } from '../helpers/api'; + + +function Dashboard() { + // Get the links from the API + const [links, setLinks] = useState([]); + useEffect(() => { + Axios.get('/api/links') + .then((res) => { + setLinks(res.data); + }) + .catch((err) => { + console.log(err); + }); + }, []); + + + return ( +
+ + + + + + + + + + + {/* {links.map((link: any) => ( + + + + + + + ))} */} + +
LinkVisitsRedirectExpire Date
{link.url}{link.visits}{link.redirect}{link.expire_date}
+
+ ) +} + +export default Dashboard; \ No newline at end of file diff --git a/app/src/components/Login.tsx b/app/src/components/Login.tsx new file mode 100644 index 0000000..8235b65 --- /dev/null +++ b/app/src/components/Login.tsx @@ -0,0 +1,84 @@ +import { useState, FormEvent } from 'react'; +import styles from '../styles/Login.module.css'; +import { Link } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import axios from 'axios'; + +function Login() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + const navigate = useNavigate(); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + try { + const res = await axios.post( + '/api/auth/token', + new URLSearchParams({ + username: username, + password: password, + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + } + ); + + if (res.status === 200) { + navigate('/dashboard'); + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const customErrorMessage = error.response?.data?.detail || null; + setPassword(''); + setError(customErrorMessage || 'An error occurred. Please try again.'); + } else { + setPassword(''); + setError('Unknown error. Please try again.'); + } + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+

Log In

+

+ {error} +

+
+
+
+
+ setUsername(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + +
+
+

Don't have an account? Create one now

+
+
+
+ ); +} + +export default Login; \ No newline at end of file diff --git a/app/src/components/Signup.tsx b/app/src/components/Signup.tsx new file mode 100644 index 0000000..293b51a --- /dev/null +++ b/app/src/components/Signup.tsx @@ -0,0 +1,106 @@ +import { useState, FormEvent } from 'react'; +import styles from '../styles/Login.module.css'; +import { Link } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import axios from 'axios'; + +function Signup() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [passwordConfirm, setPasswordConfirm] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + const navigate = useNavigate(); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + + if (password !== passwordConfirm) { + setPassword(''); + setPasswordConfirm(''); + return setError('Passwords do not match.'); + } + + try { + const res = await axios.post( + '/api/users/register', + new URLSearchParams({ + username: username, + password: password, + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + } + ); + + if (res.status === 200) { + navigate('/login'); + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const customErrorMessage = error.response?.data?.detail || null; + setUsername(''); + setPassword(''); + setPasswordConfirm(''); + setError(customErrorMessage || 'An error occurred. Please try again.'); + } else { + setUsername(''); + setPassword(''); + setPasswordConfirm(''); + setError('Unknown error. Please try again.'); + } + } finally { + setIsSubmitting(false); + } + }; + + return ( +
+

Sign up

+

+ {error} +

+
+
+
+
+ setUsername(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + setPasswordConfirm(e.target.value)} + required + /> + +
+
+

Already have an account? Log in here.

+
+
+
+ ); +} + +export default Signup; \ No newline at end of file diff --git a/app/src/helpers/api.js b/app/src/helpers/api.js new file mode 100644 index 0000000..0381f18 --- /dev/null +++ b/app/src/helpers/api.js @@ -0,0 +1,27 @@ +/** + * Accept an API endpoint, method, and body to send to the API. + * - If successful, return the response + * - If not, return false + * @param {*} endpoint API endpoint + * @param {*} method String (GET, POST, PUT, DELETE) + * @param {*} body Data to send to the API + * @returns response.json or false + */ +async function accessAPI(endpoint, method, body) { + let response = await fetch(`http://127.0.0.1:5252/api${endpoint}`, { + method: method, + credentials: 'include', + body: body, + }); + + if (response.ok) { + let data = await response.json(); + data = await data; + return data; + + } + + return false; +} + +export { accessAPI }; \ No newline at end of file diff --git a/app/src/index.css b/app/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/app/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/app/src/main.tsx b/app/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/app/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/app/src/styles/Dashboard.module.css b/app/src/styles/Dashboard.module.css new file mode 100644 index 0000000..042a2f1 --- /dev/null +++ b/app/src/styles/Dashboard.module.css @@ -0,0 +1,74 @@ +body { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + background-color: #2c3338; +} + +#container { + display: flex; + justify-content: center; + margin-top: 100px; +} + + +table { + margin: 20px 0 20px 0; + text-align: center; + font-size: 25px; + width: 1000px; + color: #ccc; + border-collapse: collapse; + overflow: hidden; +} + +/* Center all sub tables */ +.log-table-row table { + margin: 0 auto; +} + +.log-table-row table { + width: 90%; +} + +table th { + background-color: #415eac; + border: 2px solid #ccc; + padding: 10px; +} + +.link-table-row { + border: 2px solid #ccc; +} + +table td { + padding: 10px; +} + +.link-table-row td { + padding: 20px; +} + +.log-table-row table td { + background-color: #3b4148; + padding: 10px; +} + +.log-table-row table tr { + border: 2px solid #ccc; +} + +.link-button { + background-color: #3b4148; + color: #ccc; + border: none; + padding: 10px; + cursor: pointer; + font-size: 25px; + border-radius: 5px; +} + +.fa-trash:hover { + color: rgb(238, 86, 86); + cursor: pointer; +} \ No newline at end of file diff --git a/app/src/styles/Login.module.css b/app/src/styles/Login.module.css new file mode 100644 index 0000000..b2bcddf --- /dev/null +++ b/app/src/styles/Login.module.css @@ -0,0 +1,87 @@ +body { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + background-color: #2c3338; +} + +#container { + font-size: 17px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +#loginText, +#signupText { + color: #ccc; + font-size: 30px; + font-weight: 600; + border: 2px solid #606468; + padding: 10px; + border-radius: 5px; + margin: 0 auto; +} + +input { + display: block; + margin: 10px auto; + width: 300px; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #3b4148; + border: none; + font-size: 17px; +} + +button { + display: block; + margin: 10px auto; + width: 100%; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #415eac; + border: none; + font-size: 17px; + cursor: pointer; + transition: background-color 0.2s ease, transform 0.3s ease; +} + +button:hover { + background-color: #2e4781; +} + +button:active { + transform: scale(0.95); +} + +#error { + color: #ee6161; +} + +.link { + text-decoration: underline; + color: #ccc; + +} + +.link:hover { + text-decoration: none; + color: #415eac; +} + +#bottomText { + color: #606468; +} + +.visible { + visibility: visible; +} + +.hidden { + visibility: hidden; +} \ No newline at end of file diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/app/src/vite-env.d.ts @@ -0,0 +1 @@ +/// -- cgit v1.2.3-70-g09d2