mirror of
https://github.com/AmanTahiliani/PeerNotes.git
synced 2026-08-07 11:55:12 -04:00
Merge pull request #17 from AmanTahiliani/alexa-register-file-page
Alexa register file page + results page
This commit is contained in:
@@ -23,7 +23,7 @@ export default function App() {
|
|||||||
<>
|
<>
|
||||||
<SessionContext.Provider value={session}>
|
<SessionContext.Provider value={session}>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="App" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '90vh' , gap: 15}}>
|
<div className="App">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</SessionContext.Provider>
|
</SessionContext.Provider>
|
||||||
@@ -37,7 +37,7 @@ function Navbar() {
|
|||||||
background: '#B1D4E0',
|
background: '#B1D4E0',
|
||||||
padding: '8px 16px',
|
padding: '8px 16px',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
margin: '0 5px'
|
// margin: '0 5px'
|
||||||
};
|
};
|
||||||
const session = useContext(SessionContext);
|
const session = useContext(SessionContext);
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
@@ -47,10 +47,14 @@ function Navbar() {
|
|||||||
|
|
||||||
const links = session ? (
|
const links = session ? (
|
||||||
<>
|
<>
|
||||||
<img src="/PeerNotes.png" alt="PeerNotes Logo" style={{ height: '60px', marginRight: '10px' }} />
|
<div>
|
||||||
<Link to="/" style={linkStyle}>Home</Link>
|
<img src="/PeerNotes.png" alt="PeerNotes Logo" style={{ height: '60px' }} />
|
||||||
<Link to="/search" style={linkStyle}>Search</Link>
|
<Link to="/register" style={linkStyle}>Upload</Link>
|
||||||
<a onClick={logout} style={linkStyle}>Logout</a>
|
<Link to="/search" style={linkStyle}>Search</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a onClick={logout} style={linkStyle}>Logout</a>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
): (
|
): (
|
||||||
<Link to="/login">Login</Link>
|
<Link to="/login">Login</Link>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Login from './screens/Login.tsx';
|
|||||||
import Signup from './screens/Signup.tsx';
|
import Signup from './screens/Signup.tsx';
|
||||||
import Results from './screens/Results.tsx';
|
import Results from './screens/Results.tsx';
|
||||||
import MainSearch from './screens/MainSearch.tsx';
|
import MainSearch from './screens/MainSearch.tsx';
|
||||||
|
import RegisterFile from './screens/RegisterFile.tsx';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,10 @@ const router = createBrowserRouter([
|
|||||||
path: "results",
|
path: "results",
|
||||||
element: <Results/>,
|
element: <Results/>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "register",
|
||||||
|
element: <RegisterFile />,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import '../styles/MainScreenWrapper.css';
|
import '../styles/MainScreenWrapper.css';
|
||||||
import { getSessionCookie } from '../contexts/session';
|
import { getAuthHeaders } from '../utils/getAuthHeaders';
|
||||||
import { Professor, Course, Topic } from "../types/types";
|
import { Professor, Course, Topic, Semester } from "../types/types";
|
||||||
|
|
||||||
interface Filters extends Record<string, string> {
|
interface Filters extends Record<string, string> {
|
||||||
professor: string;
|
professor: string;
|
||||||
@@ -19,6 +19,7 @@ const MainSearch: React.FC = () => {
|
|||||||
const [professors, setProfessors] = useState<Professor[]>([]);
|
const [professors, setProfessors] = useState<Professor[]>([]);
|
||||||
const [courses, setCourses] = useState<Course[]>([]);
|
const [courses, setCourses] = useState<Course[]>([]);
|
||||||
const [topics, setTopics] = useState<Topic[]>([]);
|
const [topics, setTopics] = useState<Topic[]>([]);
|
||||||
|
const [semesters, setSemesters] = useState<Semester[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,17 +29,10 @@ const MainSearch: React.FC = () => {
|
|||||||
fetchCourses();
|
fetchCourses();
|
||||||
// Fetch topics
|
// Fetch topics
|
||||||
fetchTopics();
|
fetchTopics();
|
||||||
|
// Fetch semesters
|
||||||
|
fetchSemesters();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getAuthHeaders = () => {
|
|
||||||
const token = getSessionCookie();
|
|
||||||
// const token = localStorage.getItem('authToken');
|
|
||||||
console.log("Using token for API call:", token);
|
|
||||||
return {
|
|
||||||
'Authorization': `Token ${token}`,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchProfessors = async () => {
|
const fetchProfessors = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -47,10 +41,8 @@ const MainSearch: React.FC = () => {
|
|||||||
headers: getAuthHeaders(),
|
headers: getAuthHeaders(),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log("Professors fetched:", data);
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
console.log("Professors fetched:", data);
|
|
||||||
setProfessors(data);
|
setProfessors(data);
|
||||||
} else {
|
} else {
|
||||||
console.error('Data fetched is not an array:', data);
|
console.error('Data fetched is not an array:', data);
|
||||||
@@ -93,6 +85,22 @@ const MainSearch: React.FC = () => {
|
|||||||
console.error('Error fetching topics:', error);
|
console.error('Error fetching topics:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const fetchSemesters = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('http://localhost:8000/api/semesters', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
setSemesters(data);
|
||||||
|
} else {
|
||||||
|
console.error('Data fetched is not an array:', data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching courses:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
@@ -139,6 +147,15 @@ const MainSearch: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="filter-item">
|
||||||
|
<label htmlFor="semesters">Semester:</label>
|
||||||
|
<select id="semester" name="semester" value={filters.semester} onChange={handleChange}>
|
||||||
|
<option value="">Select a Semester</option>
|
||||||
|
{semesters.map((semester) => (
|
||||||
|
<option key={semester.id} value={semester.id}>{semester.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<button type="submit" className="submit-button">Submit</button>
|
<button type="submit" className="submit-button">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
|
|||||||
66
frontend/src/screens/RegisterFile.tsx
Normal file
66
frontend/src/screens/RegisterFile.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import "../styles/RegisterFile.css";
|
||||||
|
import { RegisteredFile } from "../types/types";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export default function RegisterFile() {
|
||||||
|
const [registeredFiles, setRegisteredFiles] = useState<RegisteredFile[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRegisteredFiles();
|
||||||
|
}, [])
|
||||||
|
const fetchRegisteredFiles = () => {
|
||||||
|
return
|
||||||
|
fetch("http://localhost:5000/files") // replace with local api endpoint
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setRegisteredFiles(data);
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
}
|
||||||
|
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
// handle file upload
|
||||||
|
const formData = new FormData(event.target as HTMLFormElement);
|
||||||
|
const file = formData.get('file');
|
||||||
|
console.log(file, typeof file)
|
||||||
|
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className="file-upload-card">
|
||||||
|
<h1>Upload a File</h1>
|
||||||
|
<p>Host a local file for the GT community!</p>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<label htmlFor="file">File</label>
|
||||||
|
<input type="file" name="file" id="file" />
|
||||||
|
<button type="submit" className="submit-button">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>Registered Files</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>File Name</th>
|
||||||
|
<th>Original Author</th>
|
||||||
|
<th>Professor</th>
|
||||||
|
<th>Semester</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{registeredFiles.map((file) => (
|
||||||
|
<tr key={file.id}>
|
||||||
|
<td>{file.filename}</td>
|
||||||
|
<td>{file.original_author.username}</td>
|
||||||
|
<td>{file.professor.name}</td>
|
||||||
|
<td>{file.semester.name}</td>
|
||||||
|
<td>{file.status}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import styles from '../styles/Results.module.css'; // Make sure this path is correct
|
import styles from '../styles/Results.module.css'; // Make sure this path is correct
|
||||||
|
import { getAuthHeaders } from '../utils/getAuthHeaders';
|
||||||
interface File {
|
import { File } from '../types/types';
|
||||||
id: string;
|
|
||||||
name: string; // do we even use id and name?
|
|
||||||
professor: string;
|
|
||||||
course: string;
|
|
||||||
type?: string; // Assumed type is optional
|
|
||||||
// what other properties to add?semester?
|
|
||||||
}
|
|
||||||
|
|
||||||
const Results: React.FC = () => {
|
const Results: React.FC = () => {
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
@@ -23,7 +16,8 @@ const Results: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
// Need to double check this
|
// Need to double check this
|
||||||
const queryString = location.search; // Includes the '?' prefix
|
const queryString = location.search; // Includes the '?' prefix
|
||||||
const response = await fetch(`/api/path-to-filefilterview${queryString}`);
|
console.log(queryString)
|
||||||
|
const response = await fetch(`http://localhost:8000/api/files/filter${queryString}`, {headers: getAuthHeaders()});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -55,9 +49,34 @@ const Results: React.FC = () => {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
) : files.length ? (
|
) : files.length ? (
|
||||||
<table className={styles.resultsTable}>
|
<table>
|
||||||
{/* Table structure here */}
|
<thead>
|
||||||
</table>
|
<tr>
|
||||||
|
<th>Filename</th>
|
||||||
|
<th>Course</th>
|
||||||
|
<th>Professor</th>
|
||||||
|
<th>Semester</th>
|
||||||
|
<th>Upvotes</th>
|
||||||
|
<th>Downvotes</th>
|
||||||
|
<th>Original Author</th>
|
||||||
|
<th>Download</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{files.map((file) => (
|
||||||
|
<tr key={file.id}>
|
||||||
|
<td>{file.filename}</td>
|
||||||
|
<td>{file.course.name} {file.course.number}</td>
|
||||||
|
<td>{file.professor.name}</td>
|
||||||
|
<td>{file.semester.name}</td>
|
||||||
|
<td>{file.upvotes.length}</td>
|
||||||
|
<td>{file.downvotes.length}</td>
|
||||||
|
<td>{file.original_author.username}</td>
|
||||||
|
<td><a href="">Download</a></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
) : (
|
) : (
|
||||||
<p>No results found.</p>
|
<p>No results found.</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
html {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -14,6 +17,11 @@ nav {
|
|||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
nav div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
.App {
|
.App {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -21,10 +29,12 @@ nav {
|
|||||||
justify-content: center; /* If you're centering the content */
|
justify-content: center; /* If you're centering the content */
|
||||||
height: 100vh; /* Full height */
|
height: 100vh; /* Full height */
|
||||||
margin-top: -100px; /* Move up */
|
margin-top: -100px; /* Move up */
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #0C2D48;
|
background-color: #0C2D48;
|
||||||
font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
|
font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
48
frontend/src/styles/RegisterFile.css
Normal file
48
frontend/src/styles/RegisterFile.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-upload-card {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #B1D4E0;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.file-upload-card h1 {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-card {
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #2E8BC0;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.submit-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin-top: 20px; /* Adds space above the button */
|
||||||
|
background-color: #2E8BC0; /* Example background color */
|
||||||
|
color: white; /* Text color */
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #FFFFFF;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #FFFFFF;
|
||||||
|
background-color: #B1D4E0;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
@@ -27,17 +27,33 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding-top: 100px; /* Adjust based on header height to ensure content doesn't overlap */
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 90%;
|
width: 100vh;
|
||||||
max-width: 600px;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resultsTable {
|
.resultsTable {
|
||||||
/* to be filled out */
|
/* to be filled out */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.fileCard p {
|
||||||
|
background-color: #1f4f7c;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.fileCard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
gap: 1rem;
|
||||||
|
background-color: #145DA0;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
min-width: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
.searchAgainButton {
|
.searchAgainButton {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
@@ -54,4 +70,19 @@
|
|||||||
|
|
||||||
.searchAgainButton:hover {
|
.searchAgainButton:hover {
|
||||||
background-color: #B1D4E0; /* lighter shade for hover effect */
|
background-color: #B1D4E0; /* lighter shade for hover effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #FFFFFF;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #FFFFFF;
|
||||||
|
background-color: #B1D4E0;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
@@ -12,4 +12,36 @@ export interface Topic {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Semester {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
ip_address: string;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface File {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
original_author: User;
|
||||||
|
professor: Professor;
|
||||||
|
course: Course;
|
||||||
|
semester: Semester;
|
||||||
|
upvotes: number[];
|
||||||
|
downvotes: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
HOSTED = "HOSTED",
|
||||||
|
PRIVATE = 'PRIVATE',
|
||||||
|
}
|
||||||
|
export interface RegisteredFile extends File {
|
||||||
|
status: Status;
|
||||||
}
|
}
|
||||||
8
frontend/src/utils/getAuthHeaders.ts
Normal file
8
frontend/src/utils/getAuthHeaders.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { getSessionCookie } from "../contexts/session";
|
||||||
|
export const getAuthHeaders = () => {
|
||||||
|
const token = getSessionCookie();
|
||||||
|
return {
|
||||||
|
'Authorization': `Token ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user