From 0a4af50d080e24f3bc36c07791245e687c5e4bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Sch=C3=BCtz?= Date: Thu, 31 Oct 2024 14:23:21 +0100 Subject: [PATCH] Saving --- README.md | 6 + out/main.pdf | Bin 59448 -> 59659 bytes src/backend/course_content_extractor.py | 92 +++-- src/backend/folder_structure.yaml | 12 + src/backend/main.py | 68 ++-- src/backend/moodle_downloader.py | 104 +++-- src/frontend/blackboard/.eslintrc.json | 13 +- src/frontend/blackboard/.prettierrc | 3 + src/frontend/blackboard/.vscode/launch.json | 27 ++ src/frontend/blackboard/.vscode/settings.json | 2 +- src/frontend/blackboard/app/error.tsx | 48 +-- src/frontend/blackboard/app/food/page.tsx | 79 ++++ src/frontend/blackboard/app/layout.tsx | 12 +- src/frontend/blackboard/app/page.tsx | 10 +- src/frontend/blackboard/app/providers.tsx | 35 +- src/frontend/blackboard/app/study/page.tsx | 20 + src/frontend/blackboard/app/todo/page.tsx | 78 ++++ src/frontend/blackboard/app/weather/page.tsx | 18 + .../blackboard/components/BottomNavbar.tsx | 30 +- .../blackboard/components/counter.tsx | 14 - src/frontend/blackboard/components/icons.tsx | 376 +++++++++--------- .../blackboard/components/primitives.ts | 92 ++--- .../blackboard/components/theme-switch.tsx | 140 +++---- src/frontend/blackboard/config/fonts.ts | 16 +- src/frontend/blackboard/config/site.ts | 44 +- src/frontend/blackboard/next.config.js | 13 +- src/frontend/blackboard/package.json | 112 +++--- src/frontend/blackboard/postcss.config.js | 6 +- src/frontend/blackboard/styles/globals.css | 4 - src/frontend/blackboard/tailwind.config.js | 12 +- src/frontend/blackboard/types/index.ts | 6 +- src/frontend/blackboard/uno.config.ts | 1 + 32 files changed, 891 insertions(+), 602 deletions(-) create mode 100644 src/backend/folder_structure.yaml create mode 100644 src/frontend/blackboard/.prettierrc create mode 100644 src/frontend/blackboard/.vscode/launch.json create mode 100644 src/frontend/blackboard/app/food/page.tsx create mode 100644 src/frontend/blackboard/app/study/page.tsx create mode 100644 src/frontend/blackboard/app/todo/page.tsx create mode 100644 src/frontend/blackboard/app/weather/page.tsx delete mode 100644 src/frontend/blackboard/components/counter.tsx diff --git a/README.md b/README.md index 920649d..f052d2a 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,9 @@ ### Layout ### Deployment + + + +## Hilfsmittel +- ChatGPT + - https://chatgpt.com/share/671d735f-8f40-8006-a195-1834c70412df diff --git a/out/main.pdf b/out/main.pdf index 9ef1142cec70805fac09f879d3595463af192403..51d74f99fbc7993947caa7b404c8f8ff73cadc22 100644 GIT binary patch delta 5209 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 CICE71 diff --git a/src/backend/course_content_extractor.py b/src/backend/course_content_extractor.py index cf2f20a..066edbf 100644 --- a/src/backend/course_content_extractor.py +++ b/src/backend/course_content_extractor.py @@ -1,61 +1,90 @@ +# course_content_extractor.py + import os import zipfile import shutil import tempfile import subprocess import sys +import re +import unicodedata + class CourseContentExtractor: - def __init__(self, download_dir, output_dir=None): + def __init__(self, download_dir, root_dir): self.download_dir = download_dir - self.output_dir = output_dir or os.path.join(os.getcwd(), 'data') + self.root_dir = root_dir # Read from environment variable - def extract_contents(self): - # Ensure output_dir exists - if not os.path.exists(self.output_dir): - os.makedirs(self.output_dir) + def extract_contents(self, courses): + # Ensure root_dir exists + if not os.path.exists(self.root_dir): + os.makedirs(self.root_dir) - # Find all ZIP files in download_dir + # Loop through downloaded ZIP files zip_files = [f for f in os.listdir(self.download_dir) if f.endswith('.zip')] for filename in zip_files: zip_path = os.path.join(self.download_dir, filename) base_name = os.path.splitext(filename)[0] - # Use the base name as the course folder name - course_name = base_name + # Find the course info matching the ZIP file + course_info = next((course for course in courses if course['CourseName'] == base_name), None) + if not course_info: + print(f"No matching course found for {base_name}. Skipping.") + continue - course_output_dir = os.path.join(self.output_dir, course_name) - # Ensure course_output_dir exists - if not os.path.exists(course_output_dir): - os.makedirs(course_output_dir) + # Build the folder structure + study_program = course_info['StudyProgram'] + semester = course_info['Semester'] + course_name = course_info['CourseName'] - # Create a temporary directory for extraction + course_output_dir = os.path.join( + self.root_dir, + study_program, + semester, + course_name + ) + + # Create subfolders + subfolders = ['Lectures', 'Notes', 'Summary', 'Tasks'] + for subfolder in subfolders: + subfolder_path = os.path.join(course_output_dir, subfolder) + os.makedirs(subfolder_path, exist_ok=True) + + # Create 'Code_files' subfolder under 'Tasks/' + task_name = 'Task1' # Adjust as needed or make dynamic + code_files_path = os.path.join(course_output_dir, 'Tasks', task_name, 'Code_files') + os.makedirs(code_files_path, exist_ok=True) + + # Extract and organize files with tempfile.TemporaryDirectory() as temp_extract_dir: - # Extract ZIP file to temporary directory with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(temp_extract_dir) - # Walk through the extracted files + for root, dirs, files in os.walk(temp_extract_dir): for file in files: file_path = os.path.join(root, file) if file.lower().endswith('.pdf'): - # Copy PDF files to course_output_dir - shutil.copy2(file_path, course_output_dir) + dest_folder = os.path.join(course_output_dir, 'Lectures') + shutil.copy2(file_path, dest_folder) elif file.lower().endswith(('.ppt', '.pptx')): - # Convert PowerPoint files to PDF - self.convert_ppt_to_pdf(file_path, course_output_dir) - # Delete the ZIP file after processing - os.remove(zip_path) - print(f"All PDF and PowerPoint files have been extracted to {self.output_dir}") + self.convert_ppt_to_pdf(file_path, os.path.join(course_output_dir, 'Lectures')) + elif file.lower().endswith(('.py', '.java', '.cpp', '.c', '.js', '.html', '.css')): + # Example: Place code files into 'Tasks//Code_files' + shutil.copy2(file_path, code_files_path) + else: + # Handle other file types or skip + pass + + # Delete the ZIP file after processing + os.remove(zip_path) + print(f"All files have been extracted to {self.root_dir}") def convert_ppt_to_pdf(self, ppt_path, output_dir): try: # Determine the command based on the operating system if sys.platform.startswith('win'): - # Windows systems - office_executable = 'soffice' + office_executable = 'soffice' # Ensure LibreOffice is installed and in PATH else: - # Linux and others office_executable = 'libreoffice' # Prepare the command to convert PPT/PPTX to PDF using LibreOffice @@ -76,4 +105,13 @@ class CourseContentExtractor: except FileNotFoundError: print(f"{office_executable} is not installed or not found in the system path.") # Optionally, copy the original PPT/PPTX file - shutil.copy2(ppt_path, output_dir) \ No newline at end of file + shutil.copy2(ppt_path, output_dir) + + def sanitize_filename(self, name): + # Normalize unicode characters + name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode('ASCII') + # Remove invalid characters for filenames, including newlines + sanitized = re.sub(r'[<>:"/\\|?*\n\r]+', '', name) + # Replace spaces and other problematic characters with underscores + sanitized = re.sub(r'[\s]+', '_', sanitized) + return sanitized[:100] diff --git a/src/backend/folder_structure.yaml b/src/backend/folder_structure.yaml new file mode 100644 index 0000000..545cb0f --- /dev/null +++ b/src/backend/folder_structure.yaml @@ -0,0 +1,12 @@ +root_dir: ${STUDY_MATERIAL_ROOT_DIR} # Replace with the actual environment variable pointing to your root directory + +: # Placeholder for the root of your study program (e.g., Computational_and_Data_Science) + : # The current semester (e.g., HS24) + : # The course identifier and title (e.g., cds-201_Programmierung und Prompt Engineering) + Lectures: [] # Folder for lecture materials such as PDFs or recordings (relative to the user-specified root path, e.g., /Computational_and_Data_Science/HS24/cds-201_Programmierung und Prompt Engineering/Lectures) + Notes: [] # Folder for lecture or self-study notes (relative to the user-specified root path, e.g., /Computational_and_Data_Science/HS24/cds-201_Programmierung und Prompt Engineering/Notes) + Summary: [] # Folder for summarized notes or cheat sheets (relative to the user-specified root path, e.g., /Computational_and_Data_Science/HS24/cds-201_Programmierung und Prompt Engineering/Summary) + Tasks: + # User-defined task_name + : # The specific task or assignment name, defined by the user (e.g., Task1) + Code_files: [] # Folder for code files related to the specific task (relative to the user-specified root path, e.g., /Computational_and_Data_Science/HS24/cds-201_Programmierung und Prompt Engineering/Tasks/Task1/Code_files) diff --git a/src/backend/main.py b/src/backend/main.py index cd96948..71eb815 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -1,41 +1,47 @@ -# main.py - -import logging +import os +import shutil +import tempfile from moodle_downloader import MoodleDownloader from course_content_extractor import CourseContentExtractor -import os +from dotenv import load_dotenv -# Configure logging -logging.basicConfig( - filename='moodle_downloader.log', - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s' -) -# Get credentials from environment variables -USERNAME = os.getenv('MOODLE_USERNAME') -PASSWORD = os.getenv('MOODLE_PASSWORD') +def main(): + # Load environment variables + load_dotenv() + root_dir = os.getenv('STUDY_MATERIAL_ROOT_DIR') + if not root_dir: + print("Please set the STUDY_MATERIAL_ROOT_DIR environment variable.") + return -if not USERNAME or not PASSWORD: - print("Please set the MOODLE_USERNAME and MOODLE_PASSWORD environment variables.") - exit(1) + # Use system temporary directory for downloads + with tempfile.TemporaryDirectory() as download_dir: + print(f"Using temporary download directory: {download_dir}") -# Create an instance of MoodleDownloader -downloader = MoodleDownloader(USERNAME, PASSWORD, headless=True) + # Load credentials from environment variables + username = os.getenv('MOODLE_USERNAME') + password = os.getenv('MOODLE_PASSWORD') -try: - # Login to Moodle - downloader.login() + if not username or not password: + print("Please set your Moodle credentials in environment variables.") + return - # Retrieve courses - downloader.get_courses() + # Initialize downloader + downloader = MoodleDownloader(username, password, download_dir=download_dir, headless=True) + try: + downloader.login() + downloader.get_courses() + downloader.download_all_courses() + finally: + downloader.close() - # Download all courses - downloader.download_all_courses() + # Initialize extractor + extractor = CourseContentExtractor(download_dir=download_dir, root_dir=root_dir) + extractor.extract_contents(downloader.courses) - # Extract course contents using the updated class - extractor = CourseContentExtractor(downloader.download_dir) - extractor.extract_contents() -finally: - # Close the browser - downloader.close() + # Temporary directory is automatically cleaned up here + print("Temporary download directory has been cleaned up.") + + +if __name__ == "__main__": + main() diff --git a/src/backend/moodle_downloader.py b/src/backend/moodle_downloader.py index 8d61f09..f36ca66 100644 --- a/src/backend/moodle_downloader.py +++ b/src/backend/moodle_downloader.py @@ -1,6 +1,7 @@ +# moodle_downloader.py + import os import re -import time import logging import requests import unicodedata @@ -13,20 +14,13 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException from selenium.webdriver.chrome.service import Service as ChromeService from webdriver_manager.chrome import ChromeDriverManager -import tempfile + class MoodleDownloader: def __init__(self, username, password, download_dir=None, headless=False): self.username = username self.password = password - if download_dir: - self.download_dir = download_dir - self.cleanup_download_dir = False - else: - # Create a unique temporary directory - self.temp_dir = tempfile.TemporaryDirectory() - self.download_dir = self.temp_dir.name - self.cleanup_download_dir = True + self.download_dir = download_dir # Set externally to use system temp self.headless = headless self.driver = None self.courses = [] @@ -142,25 +136,24 @@ class MoodleDownloader: try: # Get the text content full_text = coursename_element.text.strip() - lines = [line.strip() for line in full_text.split('\n') if line.strip()] - # Remove duplicates - unique_lines = list(dict.fromkeys(lines)) - # Assume the last line is the actual course name - course_name = unique_lines[-1] - - # Extract course code and term - short_name = self.extract_course_code_and_term(course_name) + # Extract course info + course_info = self.extract_course_info(full_text) course_url = coursename_element.get_attribute('href') # Check for duplicates if course_url in existing_urls: - logging.info(f"Duplicate course found: {short_name} - {course_url}") + logging.info(f"Duplicate course found: {course_info['course_name']} - {course_url}") continue existing_urls.add(course_url) - self.courses.append({'CourseName': short_name, 'URL': course_url}) - logging.info(f"Course found: {short_name} - {course_url}") + self.courses.append({ + 'StudyProgram': course_info['study_program'], + 'Semester': course_info['semester'], + 'CourseName': course_info['course_name'], + 'URL': course_url + }) + logging.info(f"Course found: {course_info['course_name']} - {course_url}") except Exception as e: logging.warning(f"Error extracting course: {e}") continue @@ -172,29 +165,29 @@ class MoodleDownloader: logging.error("An error occurred while retrieving courses.", exc_info=True) raise e - def extract_course_code_and_term(self, course_name): - # Regular expression to match course code and term - # Example course name: 'Mathematik I (cds-401) HS24' - pattern = r'\(([^)]+)\)\s+(\w+\d*)' - match = re.search(pattern, course_name) + def extract_course_info(self, course_title): + # Example course title: 'Programmierung und Prompt Engineering (cds-201) HS24' + pattern = r'^(.*?)\s*\(([^)]+)\)\s*(\w+\d*)$' + match = re.search(pattern, course_title) if match: - course_code = match.group(1) - term = match.group(2) - # Sanitize and return - return f"{self.sanitize_filename(course_code)}_{self.sanitize_filename(term)}" + study_program = 'Computational_and_Data_Science' # Replace with your actual study program if different + course_full_name = match.group(1).strip() + course_code = match.group(2).strip() + semester = match.group(3).strip() + course_identifier = f"{course_code}_{self.sanitize_filename(course_full_name)}" + return { + 'study_program': study_program, + 'semester': semester, + 'course_name': course_identifier + } else: - # If pattern doesn't match, return sanitized course name - return self.sanitize_filename(course_name) - - def sanitize_filename(self, name): - # Normalize unicode characters - name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode('ASCII') - # Remove invalid characters for filenames, including newlines - sanitized = re.sub(r'[<>:"/\\|?*\n\r]+', '', name) - # Replace spaces and other problematic characters with underscores - sanitized = re.sub(r'[\s]+', '_', sanitized) - # Truncate to a reasonable length (e.g., 100 characters) - return sanitized[:100] + # Handle cases where the pattern doesn't match + sanitized_title = self.sanitize_filename(course_title) + return { + 'study_program': 'Unknown_Program', + 'semester': 'Unknown_Semester', + 'course_name': sanitized_title + } def download_all_courses(self): if not self.courses: @@ -262,18 +255,8 @@ class MoodleDownloader: response = session.post(download_url, data=post_data, headers=headers, stream=True) response.raise_for_status() - # Attempt to extract filename from Content-Disposition header - content_disposition = response.headers.get('Content-Disposition', '') - filename = None - if content_disposition: - matches = re.findall('filename="(.+)"', content_disposition) - if matches: - filename = matches[0] - if not filename: - # If no filename in headers, use sanitized course name - filename = f"{course_name}.zip" - filename = self.sanitize_filename(filename) - + # Determine filename + filename = f"{course_name}.zip" filepath = os.path.join(self.download_dir, filename) # Overwrite existing files @@ -290,10 +273,17 @@ class MoodleDownloader: logging.error(f"Error downloading course '{course_name}': {e}", exc_info=True) continue + def sanitize_filename(self, name): + # Normalize unicode characters + name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode('ASCII') + # Remove invalid characters for filenames, including newlines + sanitized = re.sub(r'[<>:"/\\|?*\n\r]+', '', name) + # Replace spaces and other problematic characters with underscores + sanitized = re.sub(r'[\s]+', '_', sanitized) + # Truncate to a reasonable length (e.g., 100 characters) + return sanitized[:100] + def close(self): if self.driver: logging.info("Closing the browser.") self.driver.quit() - if self.cleanup_download_dir: - logging.info("Cleaning up temporary download directory.") - self.temp_dir.cleanup() \ No newline at end of file diff --git a/src/frontend/blackboard/.eslintrc.json b/src/frontend/blackboard/.eslintrc.json index d3067d4..0b48bc4 100644 --- a/src/frontend/blackboard/.eslintrc.json +++ b/src/frontend/blackboard/.eslintrc.json @@ -11,7 +11,14 @@ "plugin:react-hooks/recommended", "plugin:jsx-a11y/recommended" ], - "plugins": ["react", "unused-imports", "import", "@typescript-eslint", "jsx-a11y", "prettier"], + "plugins": [ + "react", + "unused-imports", + "import", + "@typescript-eslint", + "jsx-a11y", + "prettier" + ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaFeatures": { @@ -80,8 +87,8 @@ ], "padding-line-between-statements": [ "warn", - {"blankLine": "always", "prev": "*", "next": "return"}, - {"blankLine": "always", "prev": ["const", "let", "var"], "next": "*"}, + { "blankLine": "always", "prev": "*", "next": "return" }, + { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, { "blankLine": "any", "prev": ["const", "let", "var"], diff --git a/src/frontend/blackboard/.prettierrc b/src/frontend/blackboard/.prettierrc new file mode 100644 index 0000000..a598fca --- /dev/null +++ b/src/frontend/blackboard/.prettierrc @@ -0,0 +1,3 @@ +{ + "endOfLine": "lf" +} diff --git a/src/frontend/blackboard/.vscode/launch.json b/src/frontend/blackboard/.vscode/launch.json new file mode 100644 index 0000000..cf85782 --- /dev/null +++ b/src/frontend/blackboard/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Launch Next.js App", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev"], + "port": 9229, + "console": "integratedTerminal" + } + ], + "inputs": [ + { + "type": "promptString", + "id": "programPath", + "description": "Path to the entry point of your Next.js application (e.g., app/page.tsx)" + }, + { + "type": "promptString", + "id": "programPath", + "description": "Path to the entry point of your Next.js application (e.g., app/page.tsx)" + } + ] +} diff --git a/src/frontend/blackboard/.vscode/settings.json b/src/frontend/blackboard/.vscode/settings.json index 3662b37..25fa621 100644 --- a/src/frontend/blackboard/.vscode/settings.json +++ b/src/frontend/blackboard/.vscode/settings.json @@ -1,3 +1,3 @@ { "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/src/frontend/blackboard/app/error.tsx b/src/frontend/blackboard/app/error.tsx index 034669b..9ed5104 100644 --- a/src/frontend/blackboard/app/error.tsx +++ b/src/frontend/blackboard/app/error.tsx @@ -1,31 +1,31 @@ -'use client' +"use client"; -import { useEffect } from 'react' +import { useEffect } from "react"; export default function Error({ - error, - reset, + error, + reset, }: { - error: Error - reset: () => void + error: Error; + reset: () => void; }) { - useEffect(() => { - // Log the error to an error reporting service - /* eslint-disable no-console */ - console.error(error) - }, [error]) + useEffect(() => { + // Log the error to an error reporting service + /* eslint-disable no-console */ + console.error(error); + }, [error]); - return ( -
-

Something went wrong!

- -
- ) + return ( +
+

Something went wrong!

+ +
+ ); } diff --git a/src/frontend/blackboard/app/food/page.tsx b/src/frontend/blackboard/app/food/page.tsx new file mode 100644 index 0000000..cebe0bd --- /dev/null +++ b/src/frontend/blackboard/app/food/page.tsx @@ -0,0 +1,79 @@ +"use client"; +import { useState } from "react"; +import { Input, Button, Card, Spacer, Progress } from "@nextui-org/react"; + +// ...existing code... +export default function FoodPage() { + const [foodItems, setFoodItems] = useState< + { name: string; expirationDate: string }[] + >([]); + const [name, setName] = useState(""); + const [expirationDate, setExpirationDate] = useState(""); + + const addFoodItem = () => { + if (name.trim() && expirationDate) { + setFoodItems([...foodItems, { name: name.trim(), expirationDate }]); + setName(""); + setExpirationDate(""); + } + }; + + const getDaysRemaining = (dateString: string) => { + const today = new Date(); + const expiration = new Date(dateString); + const difference = expiration.getTime() - today.getTime(); + + return Math.ceil(difference / (1000 * 3600 * 24)); + }; + + return ( +
+

Food Glance

+ + +
+ setName(e.target.value)} + /> + setExpirationDate(e.target.value)} + /> + +
+
+ +
+ {foodItems.map((item, index) => { + const daysRemaining = getDaysRemaining(item.expirationDate); + const totalDays = + getDaysRemaining(new Date().toISOString().split("T")[0]) + + daysRemaining; + const progress = ((totalDays - daysRemaining) / totalDays) * 100; + + return ( + +
+ {item.name} + + Expires in {daysRemaining} day{daysRemaining !== 1 ? "s" : ""} + + +
+
+ ); + })} +
+
+ ); +} +// ...existing code... diff --git a/src/frontend/blackboard/app/layout.tsx b/src/frontend/blackboard/app/layout.tsx index 4445978..8194450 100644 --- a/src/frontend/blackboard/app/layout.tsx +++ b/src/frontend/blackboard/app/layout.tsx @@ -37,16 +37,12 @@ export default function RootLayout({ - -
-
- {children} -
- -
+ + {children} + diff --git a/src/frontend/blackboard/app/page.tsx b/src/frontend/blackboard/app/page.tsx index c36d5a8..d00d08a 100644 --- a/src/frontend/blackboard/app/page.tsx +++ b/src/frontend/blackboard/app/page.tsx @@ -1,3 +1,11 @@ +"use client"; +import { Text } from "@nextui-org/react"; + export default function App() { - return <>Content; + return ( +
+ Welcome to Blackboard + {/* Add additional content here */} +
+ ); } diff --git a/src/frontend/blackboard/app/providers.tsx b/src/frontend/blackboard/app/providers.tsx index d661078..41485f1 100644 --- a/src/frontend/blackboard/app/providers.tsx +++ b/src/frontend/blackboard/app/providers.tsx @@ -1,22 +1,27 @@ -'use client' +"use client"; -import * as React from 'react' -import { NextUIProvider } from '@nextui-org/system' -import { useRouter } from 'next/navigation' -import { ThemeProvider as NextThemesProvider } from 'next-themes' -import { ThemeProviderProps } from 'next-themes/dist/types' +import * as React from "react"; +import { NextUIProvider } from "@nextui-org/react"; +import { useRouter } from "next/navigation"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { ThemeProviderProps } from "next-themes/dist/types"; export interface ProvidersProps { - children: React.ReactNode - themeProps?: ThemeProviderProps + children: React.ReactNode; + themeProps?: ThemeProviderProps; } export function Providers({ children, themeProps }: ProvidersProps) { - const router = useRouter() - - return ( - - {children} - - ) + return ( + + + {children} + + + ); } diff --git a/src/frontend/blackboard/app/study/page.tsx b/src/frontend/blackboard/app/study/page.tsx new file mode 100644 index 0000000..a4bb956 --- /dev/null +++ b/src/frontend/blackboard/app/study/page.tsx @@ -0,0 +1,20 @@ +"use client"; +import { Card, Text } from "@nextui-org/react"; + +// ...existing code... +export default function StudyPage() { + return ( +
+ Study Rundown + + + + {/* Add study-related content here */} + Coming soon... + + + +
+ ); +} +// ...existing code... diff --git a/src/frontend/blackboard/app/todo/page.tsx b/src/frontend/blackboard/app/todo/page.tsx new file mode 100644 index 0000000..d30d429 --- /dev/null +++ b/src/frontend/blackboard/app/todo/page.tsx @@ -0,0 +1,78 @@ +"use client"; +import { useState } from "react"; +import { + Input, + Button, + Checkbox, + Card, + Spacer +} from "@nextui-org/react"; + +export default function TodoPage() { + const [todos, setTodos] = useState<{ text: string; completed: boolean }[]>([]); + const [input, setInput] = useState(""); + + const addTodo = () => { + if (input.trim()) { + setTodos([...todos, { text: input.trim(), completed: false }]); + setInput(""); + } + }; + + const toggleTodo = (index: number) => { + setTodos( + todos.map((todo, i) => + i === index ? { ...todo, completed: !todo.completed } : todo + ) + ); + }; + + const removeTodo = (index: number) => { + setTodos(todos.filter((_, i) => i !== index)); + }; + + return ( +
+ Todo List +

Todo List

+
+ setInput(e.target.value)} + /> + +
+ +
+ {todos.map((todo, index) => ( + + + toggleTodo(index)} + > + + + {todo.text} + + + + + + ))} +
+
+ ); +} diff --git a/src/frontend/blackboard/app/weather/page.tsx b/src/frontend/blackboard/app/weather/page.tsx new file mode 100644 index 0000000..f37d5c5 --- /dev/null +++ b/src/frontend/blackboard/app/weather/page.tsx @@ -0,0 +1,18 @@ +"use client"; +import { Card } from "@nextui-org/react"; + +// ...existing code... +export default function WeatherPage() { + return ( +
+

Weather

+ + + {/* Add weather-related content here */} + Current weather data will be displayed here. + + +
+ ); +} +// ...existing code... diff --git a/src/frontend/blackboard/components/BottomNavbar.tsx b/src/frontend/blackboard/components/BottomNavbar.tsx index e8a5e64..9bdf672 100644 --- a/src/frontend/blackboard/components/BottomNavbar.tsx +++ b/src/frontend/blackboard/components/BottomNavbar.tsx @@ -1,14 +1,28 @@ import Link from "next/link"; +import { Navbar, Text } from "@nextui-org/react"; + +import { siteConfig } from "../config/site"; -import { siteConfig } from "@/config/site"; export default function BottomNavbar() { return ( -
- {siteConfig.navItems.map((item) => ( - - {item.label} - - ))} -
+ + + {siteConfig.navItems.map((item) => { + const [icon, ...labelParts] = item.label.split(" "); + const label = labelParts.join(" "); + + return ( + + +
+ {icon} + {label} +
+ +
+ ); + })} +
+
); } diff --git a/src/frontend/blackboard/components/counter.tsx b/src/frontend/blackboard/components/counter.tsx deleted file mode 100644 index 9daf54e..0000000 --- a/src/frontend/blackboard/components/counter.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client' - -import { useState } from 'react' -import { Button } from '@nextui-org/button' - -export const Counter = () => { - const [count, setCount] = useState(0) - - return ( - - ) -} diff --git a/src/frontend/blackboard/components/icons.tsx b/src/frontend/blackboard/components/icons.tsx index 3d71983..a3d7332 100644 --- a/src/frontend/blackboard/components/icons.tsx +++ b/src/frontend/blackboard/components/icons.tsx @@ -1,215 +1,215 @@ -import * as React from 'react' +import * as React from "react"; -import { IconSvgProps } from '@/types' +import { IconSvgProps } from "@/types"; export const Logo: React.FC = ({ - size = 36, - width, - height, - ...props + size = 36, + width, + height, + ...props }) => ( - - - -) + + + +); export const DiscordIcon: React.FC = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }) => { - return ( - - - - ) -} + return ( + + + + ); +}; export const TwitterIcon: React.FC = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }) => { - return ( - - - - ) -} + return ( + + + + ); +}; export const GithubIcon: React.FC = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }) => { - return ( - - - - ) -} + return ( + + + + ); +}; export const MoonFilledIcon = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }: IconSvgProps) => ( - -) + +); export const SunFilledIcon = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }: IconSvgProps) => ( - -) + +); export const HeartFilledIcon = ({ - size = 24, - width, - height, - ...props + size = 24, + width, + height, + ...props }: IconSvgProps) => ( - -) + +); export const SearchIcon = (props: IconSvgProps) => ( - -) + +); export const NextUILogo: React.FC = (props) => { - const { width, height = 40 } = props + const { width, height = 40 } = props; - return ( - - - - - - ) -} + return ( + + + + + + ); +}; diff --git a/src/frontend/blackboard/components/primitives.ts b/src/frontend/blackboard/components/primitives.ts index 72cd159..472973c 100644 --- a/src/frontend/blackboard/components/primitives.ts +++ b/src/frontend/blackboard/components/primitives.ts @@ -1,53 +1,53 @@ -import { tv } from 'tailwind-variants' +import { tv } from "tailwind-variants"; export const title = tv({ - base: 'tracking-tight inline font-semibold', - variants: { - color: { - violet: 'from-[#FF1CF7] to-[#b249f8]', - yellow: 'from-[#FF705B] to-[#FFB457]', - blue: 'from-[#5EA2EF] to-[#0072F5]', - cyan: 'from-[#00b7fa] to-[#01cfea]', - green: 'from-[#6FEE8D] to-[#17c964]', - pink: 'from-[#FF72E1] to-[#F54C7A]', - foreground: 'dark:from-[#FFFFFF] dark:to-[#4B4B4B]', - }, - size: { - sm: 'text-3xl lg:text-4xl', - md: 'text-[2.3rem] lg:text-5xl leading-9', - lg: 'text-4xl lg:text-6xl', - }, - fullWidth: { - true: 'w-full block', - }, + base: "tracking-tight inline font-semibold", + variants: { + color: { + violet: "from-[#FF1CF7] to-[#b249f8]", + yellow: "from-[#FF705B] to-[#FFB457]", + blue: "from-[#5EA2EF] to-[#0072F5]", + cyan: "from-[#00b7fa] to-[#01cfea]", + green: "from-[#6FEE8D] to-[#17c964]", + pink: "from-[#FF72E1] to-[#F54C7A]", + foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]", }, - defaultVariants: { - size: 'md', + size: { + sm: "text-3xl lg:text-4xl", + md: "text-[2.3rem] lg:text-5xl leading-9", + lg: "text-4xl lg:text-6xl", }, - compoundVariants: [ - { - color: [ - 'violet', - 'yellow', - 'blue', - 'cyan', - 'green', - 'pink', - 'foreground', - ], - class: 'bg-clip-text text-transparent bg-gradient-to-b', - }, - ], -}) + fullWidth: { + true: "w-full block", + }, + }, + defaultVariants: { + size: "md", + }, + compoundVariants: [ + { + color: [ + "violet", + "yellow", + "blue", + "cyan", + "green", + "pink", + "foreground", + ], + class: "bg-clip-text text-transparent bg-gradient-to-b", + }, + ], +}); export const subtitle = tv({ - base: 'w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full', - variants: { - fullWidth: { - true: '!w-full', - }, + base: "w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full", + variants: { + fullWidth: { + true: "!w-full", }, - defaultVariants: { - fullWidth: true, - }, -}) + }, + defaultVariants: { + fullWidth: true, + }, +}); diff --git a/src/frontend/blackboard/components/theme-switch.tsx b/src/frontend/blackboard/components/theme-switch.tsx index dd04644..55bcb0f 100644 --- a/src/frontend/blackboard/components/theme-switch.tsx +++ b/src/frontend/blackboard/components/theme-switch.tsx @@ -1,81 +1,81 @@ -'use client' +"use client"; -import { FC } from 'react' -import { VisuallyHidden } from '@react-aria/visually-hidden' -import { SwitchProps, useSwitch } from '@nextui-org/switch' -import { useTheme } from 'next-themes' -import { useIsSSR } from '@react-aria/ssr' -import clsx from 'clsx' +import { FC } from "react"; +import { VisuallyHidden } from "@react-aria/visually-hidden"; +import { SwitchProps, useSwitch } from "@nextui-org/switch"; +import { useTheme } from "next-themes"; +import { useIsSSR } from "@react-aria/ssr"; +import clsx from "clsx"; -import { SunFilledIcon, MoonFilledIcon } from '@/components/icons' +import { SunFilledIcon, MoonFilledIcon } from "@/components/icons"; export interface ThemeSwitchProps { - className?: string - classNames?: SwitchProps['classNames'] + className?: string; + classNames?: SwitchProps["classNames"]; } export const ThemeSwitch: FC = ({ - className, - classNames, + className, + classNames, }) => { - const { theme, setTheme } = useTheme() - const isSSR = useIsSSR() + const { theme, setTheme } = useTheme(); + const isSSR = useIsSSR(); - const onChange = () => { - theme === 'light' ? setTheme('dark') : setTheme('light') - } + const onChange = () => { + theme === "light" ? setTheme("dark") : setTheme("light"); + }; - const { - Component, - slots, - isSelected, - getBaseProps, - getInputProps, - getWrapperProps, - } = useSwitch({ - isSelected: theme === 'light' || isSSR, - 'aria-label': `Switch to ${theme === 'light' || isSSR ? 'dark' : 'light'} mode`, - onChange, - }) + const { + Component, + slots, + isSelected, + getBaseProps, + getInputProps, + getWrapperProps, + } = useSwitch({ + isSelected: theme === "light" || isSSR, + "aria-label": `Switch to ${theme === "light" || isSSR ? "dark" : "light"} mode`, + onChange, + }); - return ( - - - - -
- {!isSelected || isSSR ? ( - - ) : ( - - )} -
-
- ) -} + return ( + + + + +
+ {!isSelected || isSSR ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/src/frontend/blackboard/config/fonts.ts b/src/frontend/blackboard/config/fonts.ts index 9b53d3c..85b89d1 100644 --- a/src/frontend/blackboard/config/fonts.ts +++ b/src/frontend/blackboard/config/fonts.ts @@ -1,11 +1,11 @@ -import { Fira_Code as FontMono, Inter as FontSans } from 'next/font/google' +import { Inter } from "next/font/google"; -export const fontSans = FontSans({ - subsets: ['latin'], - variable: '--font-sans', -}) +export const fontSans = Inter({ + subsets: ["latin"], + variable: "--font-sans", +}); export const fontMono = FontMono({ - subsets: ['latin'], - variable: '--font-mono', -}) + subsets: ["latin"], + variable: "--font-mono", +}); diff --git a/src/frontend/blackboard/config/site.ts b/src/frontend/blackboard/config/site.ts index c41c112..9ff1e84 100644 --- a/src/frontend/blackboard/config/site.ts +++ b/src/frontend/blackboard/config/site.ts @@ -1,24 +1,24 @@ -export type SiteConfig = typeof siteConfig +export type SiteConfig = typeof siteConfig; export const siteConfig = { - name: 'Blackboard', - description: 'Overview of the day', - navItems: [ - { - label: '📝 Todo', - href: '/todo', - }, - { - label: '🍔 Food Glance', - href: '/food', - }, - { - label: '🤖 Study Rundown', - href: '/study', - }, - { - label: 'Weather', - href: '/weather', - }, - ], -} + name: "Blackboard", + description: "Overview of the day", + navItems: [ + { + label: "📝 Todo", + href: "/todo", + }, + { + label: "🍔 Food Glance", + href: "/food", + }, + { + label: "🤖 Study Rundown", + href: "/study", + }, + { + label: "☁️ Weather", + href: "/weather", + }, + ], +}; diff --git a/src/frontend/blackboard/next.config.js b/src/frontend/blackboard/next.config.js index 6737660..6a9d2de 100644 --- a/src/frontend/blackboard/next.config.js +++ b/src/frontend/blackboard/next.config.js @@ -1,15 +1,6 @@ // next.config.js -const UnoCSS = require('@unocss/webpack').default /** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - webpack: (config) => { - config.plugins.push( - UnoCSS(), - ) - return config - }, -} +const nextConfig = {}; -module.exports = nextConfig \ No newline at end of file +module.exports = nextConfig; diff --git a/src/frontend/blackboard/package.json b/src/frontend/blackboard/package.json index 3af6c7d..8f147d1 100644 --- a/src/frontend/blackboard/package.json +++ b/src/frontend/blackboard/package.json @@ -1,56 +1,60 @@ { - "name": "next-app-template", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "next dev --turbo", - "build": "next build", - "start": "next start", - "lint": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix" - }, - "dependencies": { - "@nextui-org/button": "2.0.38", - "@nextui-org/code": "2.0.33", - "@nextui-org/input": "2.2.5", - "@nextui-org/kbd": "2.0.34", - "@nextui-org/link": "2.0.35", - "@nextui-org/listbox": "2.1.27", - "@nextui-org/navbar": "2.0.37", - "@nextui-org/react": "^2.4.8", - "@nextui-org/snippet": "2.0.43", - "@nextui-org/switch": "2.0.34", - "@nextui-org/system": "2.2.6", - "@nextui-org/theme": "2.2.11", - "@react-aria/ssr": "3.9.4", - "@react-aria/visually-hidden": "3.8.12", - "clsx": "2.1.1", - "framer-motion": "~11.1.1", - "intl-messageformat": "^10.5.0", - "next": "14.2.4", - "next-themes": "^0.2.1", - "react": "18.3.1", - "react-dom": "18.3.1" - }, - "devDependencies": { - "@types/node": "20.5.7", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "@typescript-eslint/eslint-plugin": "7.2.0", - "@typescript-eslint/parser": "7.2.0", - "autoprefixer": "10.4.19", - "eslint": "^8.57.0", - "eslint-config-next": "14.2.1", - "eslint-config-prettier": "^8.2.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.23.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-unused-imports": "^3.2.0", - "postcss": "8.4.38", - "tailwind-variants": "0.1.20", - "tailwindcss": "3.4.3", - "typescript": "5.0.4" - } + "name": "next-app-template", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "next dev --turbo", + "build": "next build", + "start": "next start", + "lint": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix" + }, + "dependencies": { + "@nextui-org/button": "2.0.38", + "@nextui-org/code": "2.0.33", + "@nextui-org/input": "2.2.5", + "@nextui-org/kbd": "2.0.34", + "@nextui-org/link": "2.0.35", + "@nextui-org/listbox": "2.1.27", + "@nextui-org/navbar": "2.0.37", + "@nextui-org/react": "^2.4.8", + "@nextui-org/snippet": "2.0.43", + "@nextui-org/switch": "2.0.34", + "@nextui-org/system": "2.2.6", + "@nextui-org/theme": "2.2.11", + "@react-aria/ssr": "3.9.4", + "@react-aria/visually-hidden": "3.8.12", + "@unocss/webpack": "^0.63.6", + "clsx": "2.1.1", + "framer-motion": "~11.1.1", + "intl-messageformat": "^10.5.0", + "next": "^15.0.2", + "next-themes": "^0.2.1", + "react": "18.3.1", + "react-dom": "18.3.1", + "react-icons": "^5.3.0" + }, + "devDependencies": { + "@types/node": "20.5.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "7.2.0", + "@typescript-eslint/parser": "7.2.0", + "@unocss/postcss": "^0.63.6", + "autoprefixer": "10.4.19", + "eslint": "^8.57.0", + "eslint-config-next": "14.2.1", + "eslint-config-prettier": "^8.2.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.23.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-unused-imports": "^3.2.0", + "postcss": "8.4.38", + "tailwind-variants": "0.1.20", + "tailwindcss": "3.4.3", + "typescript": "5.0.4", + "unocss": "^0.63.6" + } } diff --git a/src/frontend/blackboard/postcss.config.js b/src/frontend/blackboard/postcss.config.js index 33ad091..ad71d24 100644 --- a/src/frontend/blackboard/postcss.config.js +++ b/src/frontend/blackboard/postcss.config.js @@ -1,6 +1,10 @@ module.exports = { plugins: { + "@unocss/postcss": { + // Optional + content: ["**/*.{html,js,ts,jsx,tsx}"], + }, tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/src/frontend/blackboard/styles/globals.css b/src/frontend/blackboard/styles/globals.css index d347afe..b5c61c9 100644 --- a/src/frontend/blackboard/styles/globals.css +++ b/src/frontend/blackboard/styles/globals.css @@ -1,7 +1,3 @@ -@import '@unocss/reset/tailwind.css'; - @tailwind base; @tailwind components; @tailwind utilities; - -@unocss all; \ No newline at end of file diff --git a/src/frontend/blackboard/tailwind.config.js b/src/frontend/blackboard/tailwind.config.js index 573fa4c..0deb46f 100644 --- a/src/frontend/blackboard/tailwind.config.js +++ b/src/frontend/blackboard/tailwind.config.js @@ -1,11 +1,11 @@ -import {nextui} from '@nextui-org/theme' +import { nextui } from "@nextui-org/theme"; /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', - './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}' + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { @@ -17,5 +17,5 @@ module.exports = { }, darkMode: "class", darkMode: "class", - plugins: [nextui()], -} + plugins: [nextui()], +}; diff --git a/src/frontend/blackboard/types/index.ts b/src/frontend/blackboard/types/index.ts index aab3c21..cece4a4 100644 --- a/src/frontend/blackboard/types/index.ts +++ b/src/frontend/blackboard/types/index.ts @@ -1,5 +1,5 @@ -import { SVGProps } from 'react' +import { SVGProps } from "react"; export type IconSvgProps = SVGProps & { - size?: number -} + size?: number; +}; diff --git a/src/frontend/blackboard/uno.config.ts b/src/frontend/blackboard/uno.config.ts index 9948b76..29acbcc 100644 --- a/src/frontend/blackboard/uno.config.ts +++ b/src/frontend/blackboard/uno.config.ts @@ -1,3 +1,4 @@ +// @filename uno.config.ts import { defineConfig, presetUno } from "unocss"; export default defineConfig({