aboutsummaryrefslogtreecommitdiff
path: root/app/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/components')
-rw-r--r--app/src/components/Dashboard.tsx233
-rw-r--r--app/src/components/Login.tsx151
-rw-r--r--app/src/components/Signup.tsx189
3 files changed, 299 insertions, 274 deletions
diff --git a/app/src/components/Dashboard.tsx b/app/src/components/Dashboard.tsx
index ccbfd99..851308e 100644
--- a/app/src/components/Dashboard.tsx
+++ b/app/src/components/Dashboard.tsx
@@ -6,124 +6,139 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
function Dashboard() {
- document.title = 'LinkLogger | Dashboard'
+ document.title = 'LinkLogger | Dashboard';
- interface Log {
- id: number;
- link: string;
- timestamp: string;
- ip: string;
- location: string;
- browser: string;
- os: string;
- userAgent: string;
- isp: string;
- }
+ interface Log {
+ id: number;
+ link: string;
+ timestamp: string;
+ ip: string;
+ location: string;
+ browser: string;
+ os: string;
+ userAgent: string;
+ isp: string;
+ }
- interface Link {
- link: string;
- owner: number;
- redirect_link: string;
- expire_date: string;
- }
+ interface Link {
+ link: string;
+ owner: number;
+ redirect_link: string;
+ expire_date: string;
+ }
- const [links, setLinks] = useState<Link[]>([]);
- const [logs, setLogs] = useState<Log[]>([]);
- const [visibleLog, setVisibleLog] = useState<string | null>(null);
- const navigate = useNavigate();
+ const [links, setLinks] = useState<Link[]>([]);
+ const [logs, setLogs] = useState<Log[]>([]);
+ const [visibleLog, setVisibleLog] = useState<string | null>(null);
+ const navigate = useNavigate();
- // Fetch links from API
- useEffect(() => {
- Axios.get('/api/links').then((res) => {
- if (res.status === 200) {
- setLinks(res.data);
- } else {
- navigate('/login');
- }
- }).catch(() => {
- navigate('/login');
- });
- }, []);
+ // Fetch links from API
+ useEffect(() => {
+ Axios.get('/api/links')
+ .then((res) => {
+ if (res.status === 200) {
+ setLinks(res.data);
+ } else {
+ navigate('/login');
+ }
+ })
+ .catch(() => {
+ navigate('/login');
+ });
+ }, []);
- // Fetch logs from API
- useEffect(() => {
- Axios.get('/api/logs').then((res) => {
- if (res.status === 200) {
- setLogs(res.data);
- } else {
- navigate('/login');
- }
- }).catch(() => {
- navigate('/login');
- });
- }, []);
+ // Fetch logs from API
+ useEffect(() => {
+ Axios.get('/api/logs')
+ .then((res) => {
+ if (res.status === 200) {
+ setLogs(res.data);
+ } else {
+ navigate('/login');
+ }
+ })
+ .catch(() => {
+ navigate('/login');
+ });
+ }, []);
+ const toggleLogRow = (link: string) => {
+ setVisibleLog(visibleLog === link ? null : link);
+ };
- const toggleLogRow = (link: string) => {
- setVisibleLog(visibleLog === link ? null : link);
- };
+ return (
+ <table id={styles.mainTable}>
+ <thead>
+ <tr style={{ border: '2px solid #ccc' }}>
+ <th>Link</th>
+ <th>Visits</th>
+ <th>Redirect</th>
+ <th>Expire Date</th>
+ </tr>
+ </thead>
+ <tbody>
+ {/* For every link and its logs */}
+ {links.map((link) => (
+ <React.Fragment key={link.link}>
+ <tr className={styles.linkTableRow}>
+ <td>
+ <button
+ onClick={() => toggleLogRow(link.link)}
+ className={styles.linkButton}
+ >
+ {link.link}
+ </button>
+ </td>
+ <td>
+ {logs.filter((log) => log.link === link.link).length || 0}
+ </td>
+ <td>{link.redirect_link}</td>
+ <td>{link.expire_date}</td>
+ </tr>
- return (
- <table id={styles.mainTable}>
- <thead>
- <tr style={{ border: '2px solid #ccc' }}>
- <th>Link</th>
- <th>Visits</th>
- <th>Redirect</th>
- <th>Expire Date</th>
- </tr>
- </thead>
- <tbody>
- {/* For every link and its logs */}
- {links.map((link) => (
- <React.Fragment key={link.link}>
- <tr className={styles.linkTableRow}>
+ {/* Conditionally render logs for this link */}
+ {visibleLog === link.link && (
+ <tr className={styles.logTableRow}>
+ <td colSpan={6}>
+ <table>
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Timestamp</th>
+ <th>IP</th>
+ <th>Location</th>
+ <th colSpan={2}>ISP</th>
+ </tr>
+ </thead>
+ <tbody>
+ {/* Render logs only if visibleLog matches the link */}
+ {logs
+ .filter((log) => log.link === link.link)
+ .map((log, index, filteredLogs) => (
+ <tr key={log.id}>
+ <td>{filteredLogs.length - index}</td>
+ <td>{log.timestamp}</td>
+ <td>{log.ip}</td>
+ <td>{log.location}</td>
+ <td>{log.isp}</td>
<td>
- <button onClick={() => toggleLogRow(link.link)} className={styles.linkButton}>{link.link}</button>
+ <FontAwesomeIcon
+ icon={faTrash}
+ className={styles.trashBin}
+ />
</td>
- <td>{logs.filter((log) => log.link === link.link).length || 0}</td>
- <td>{link.redirect_link}</td>
- <td>{link.expire_date}</td>
- </tr>
-
- {/* Conditionally render logs for this link */}
- {visibleLog === link.link && (
- <tr className={styles.logTableRow}>
- <td colSpan={6}>
- <table>
- <thead>
- <tr>
- <th>ID</th>
- <th>Timestamp</th>
- <th>IP</th>
- <th>Location</th>
- <th colSpan={2}>ISP</th>
- </tr>
- </thead>
- <tbody>
- {/* Render logs only if visibleLog matches the link */}
- {logs
- .filter((log) => log.link === link.link)
- .map((log, index, filteredLogs) => (
- <tr key={log.id}>
- <td>{filteredLogs.length - index}</td>
- <td>{log.timestamp}</td>
- <td>{log.ip}</td>
- <td>{log.location}</td>
- <td>{log.isp}</td>
- <td><FontAwesomeIcon icon={faTrash} className={styles.trashBin}/></td>
- </tr>
- ))}
- </tbody>
- </table>
- </td>
- </tr>
- )}
- </React.Fragment>
- ))}
- </tbody>
- </table>
- )
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ )}
+ </React.Fragment>
+ ))}
+ </tbody>
+ </table>
+ );
}
-export default Dashboard; \ No newline at end of file
+export default Dashboard;
diff --git a/app/src/components/Login.tsx b/app/src/components/Login.tsx
index a3e5cf9..2a81295 100644
--- a/app/src/components/Login.tsx
+++ b/app/src/components/Login.tsx
@@ -5,82 +5,87 @@ import { useNavigate } from 'react-router-dom';
import axios from 'axios';
function Login() {
- document.title = 'LinkLogger | Login'
+ document.title = 'LinkLogger | Login';
- const [username, setUsername] = useState('');
- const [password, setPassword] = useState('');
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [error, setError] = useState<string | null>(null);
- const navigate = useNavigate();
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+ const navigate = useNavigate();
- const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
- 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);
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
+ 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 (
- <div id={styles.container}>
- <p id={styles.loginText}>Log In</p>
- <p id={styles.error} className={error ? 'visible' : 'hidden'}>
- {error}
- </p>
- <div>
- <header>
- <hr></hr>
- <form onSubmit={handleSubmit}>
- <input
- type="text"
- placeholder="username"
- value={username}
- onChange={(e) => setUsername(e.target.value)}
- required
- />
- <input
- type="password"
- placeholder="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- required
- />
- <button type="submit" disabled={isSubmitting}>
- {isSubmitting ? 'Submitting...' : 'Submit'}
- </button>
- </form>
- <hr></hr>
- <p id={styles.bottomText}>Don't have an account? <Link to="/signup" className={styles.link}>Create one now</Link></p>
- </header>
- </div>
- </div>
- );
+ return (
+ <div id={styles.container}>
+ <p id={styles.loginText}>Log In</p>
+ <p id={styles.error} className={error ? 'visible' : 'hidden'}>
+ {error}
+ </p>
+ <div>
+ <header>
+ <hr></hr>
+ <form onSubmit={handleSubmit}>
+ <input
+ type="text"
+ placeholder="username"
+ value={username}
+ onChange={(e) => setUsername(e.target.value)}
+ required
+ />
+ <input
+ type="password"
+ placeholder="password"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ required
+ />
+ <button type="submit" disabled={isSubmitting}>
+ {isSubmitting ? 'Submitting...' : 'Submit'}
+ </button>
+ </form>
+ <hr></hr>
+ <p id={styles.bottomText}>
+ Don't have an account?{' '}
+ <Link to="/signup" className={styles.link}>
+ Create one now
+ </Link>
+ </p>
+ </header>
+ </div>
+ </div>
+ );
}
-export default Login; \ No newline at end of file
+export default Login;
diff --git a/app/src/components/Signup.tsx b/app/src/components/Signup.tsx
index 547fa9e..388396c 100644
--- a/app/src/components/Signup.tsx
+++ b/app/src/components/Signup.tsx
@@ -5,104 +5,109 @@ import { useNavigate } from 'react-router-dom';
import axios from 'axios';
function Signup() {
- document.title = 'LinkLogger | Signup'
+ document.title = 'LinkLogger | Signup';
- const [username, setUsername] = useState('');
- const [password, setPassword] = useState('');
- const [passwordConfirm, setPasswordConfirm] = useState('');
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [error, setError] = useState<string | null>(null);
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordConfirm, setPasswordConfirm] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState<string | null>(null);
- const navigate = useNavigate();
+ const navigate = useNavigate();
- const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
- e.preventDefault();
- setIsSubmitting(true);
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
+ 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 (password !== passwordConfirm) {
+ setPassword('');
+ setPasswordConfirm('');
+ return setError('Passwords do not match.');
+ }
- 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);
+ 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 (
- <div id={styles.container}>
- <p id={styles.signupText}>Sign up</p>
- <p id={styles.error} className={error ? 'visible' : 'hidden'}>
- {error}
- </p>
- <div>
- <header>
- <hr></hr>
- <form onSubmit={handleSubmit}>
- <input
- type="text"
- placeholder="username"
- value={username}
- onChange={(e) => setUsername(e.target.value)}
- required
- />
- <input
- type="password"
- placeholder="password"
- value={password}
- minLength={8}
- onChange={(e) => setPassword(e.target.value)}
- required
- />
- <input
- type="password"
- placeholder="confirm password"
- value={passwordConfirm}
- minLength={8}
- onChange={(e) => setPasswordConfirm(e.target.value)}
- required
- />
- <button type="submit" disabled={isSubmitting}>
- {isSubmitting ? 'Submitting...' : 'Submit'}
- </button>
- </form>
- <hr></hr>
- <p id={styles.bottomText}>Already have an account? <Link to="/login" className={styles.link}>Log in here.</Link></p>
- </header>
- </div>
- </div>
- );
+ return (
+ <div id={styles.container}>
+ <p id={styles.signupText}>Sign up</p>
+ <p id={styles.error} className={error ? 'visible' : 'hidden'}>
+ {error}
+ </p>
+ <div>
+ <header>
+ <hr></hr>
+ <form onSubmit={handleSubmit}>
+ <input
+ type="text"
+ placeholder="username"
+ value={username}
+ onChange={(e) => setUsername(e.target.value)}
+ required
+ />
+ <input
+ type="password"
+ placeholder="password"
+ value={password}
+ minLength={8}
+ onChange={(e) => setPassword(e.target.value)}
+ required
+ />
+ <input
+ type="password"
+ placeholder="confirm password"
+ value={passwordConfirm}
+ minLength={8}
+ onChange={(e) => setPasswordConfirm(e.target.value)}
+ required
+ />
+ <button type="submit" disabled={isSubmitting}>
+ {isSubmitting ? 'Submitting...' : 'Submit'}
+ </button>
+ </form>
+ <hr></hr>
+ <p id={styles.bottomText}>
+ Already have an account?{' '}
+ <Link to="/login" className={styles.link}>
+ Log in here.
+ </Link>
+ </p>
+ </header>
+ </div>
+ </div>
+ );
}
-export default Signup; \ No newline at end of file
+export default Signup;