Compare commits

..

No commits in common. "dev" and "master" have entirely different histories.
dev ... master

12 changed files with 298 additions and 758 deletions

11
.gitignore vendored
View File

@ -1,13 +1,2 @@
# Don't commit git
.git
# Remove database
formdata.db
# tmp files
output.txt
typer.py
README.md
go.mod
go.sum
demo.html

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/FormMaker.iml generated
View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/FormMaker.iml" filepath="$PROJECT_DIR$/.idea/FormMaker.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -10,26 +10,23 @@
<div class="container">
<h1>Submitted Answers</h1>
<button id="dark-mode-toggle">Toggle Dark Mode</button>
<table>
<tr>
<th>Question</th>
<th>Answer</th>
<th>Tags</th>
</tr>
{{range .}}
<tr>
<td>{{.QuestionText}}</td>
<td>{{.Answer}}</td>
<td>{{.Tags}}</td>
</tr>
{{else}}
<tr>
<td colspan="3">No submissions found.</td>
<td colspan="2">No submissions found.</td>
</tr>
{{end}}
</table>
</div>
<script src="/static/theme.js"></script>

View File

@ -3,96 +3,23 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Find Classmates</title>
<title>Form</title>
<link rel="stylesheet" href="/static/style.css">
<script>
/*to prevent Firefox FOUC, this must be here*/
let FF_FOUC_FIX;
</script>
</head>
<body>
<div class="container" id="main-container">
<div class="form-section" id="form-section">
<h1>Enter Your Information</h1>
<form action="/submit" method="post">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
<div class="container">
<h1>Submit Your Information</h1>
<button id="dark-mode-toggle">Toggle Dark Mode</button>
<form action="/submit" method="post">
{{range .}}
<label>{{.QuestionText}}:</label>
<input type="{{.QuestionType}}" name="custom_{{.ID}}" required><br><br>
{{end}}
<label for="email">Email:</label>
<input type="email" id="email" name="email">
<label for="phone">Phone:</label>
<input type="text" id="phone" name="phone">
<label for="dorm">Dorm:</label>
<input type="text" id="dorm" name="dorm">
<label>Classes:</label>
<div class="tags-input-container" id="classes-container">
<input type="text" id="classes-input" placeholder="Enter classes">
</div>
<input type="submit" value="Submit">
</form>
</div>
<!-- Hidden matches section until classes are entered -->
<div class="matches-section" id="matches-section">
<h2>People with Similar Classes</h2>
<div id="class-filter-container">
<p>Filter by classes:</p>
</div>
<div id="matches-container">
<p>No one has a similar class yet!</p>
</div>
</div>
<input type="submit" value="Submit">
</form>
</div>
<script src="/static/util.js"></script>
<script>
const classInput = document.getElementById('classes-input');
const formSection = document.getElementById('form-section');
const matchesSection = document.getElementById('matches-section');
const mainContainer = document.getElementById('main-container');
const tagsInputContainer = document.getElementById('classes-container');
// Add event listener to handle Enter key for adding chips
classInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && classInput.value.trim() !== "") {
event.preventDefault();
addClassChip(classInput.value.trim());
classInput.value = ''; // Clear input after adding
}
});
// Function to add a chip (tag)
function addClassChip(className) {
const tag = document.createElement('div');
tag.classList.add('tag');
tag.innerHTML = className + '<span class="close">x</span>';
// Append the new tag to the tags container
tagsInputContainer.insertBefore(tag, classInput);
// Check if any chips exist, and show the matches section
updateMatchesVisibility();
// Remove tag on close click
tag.querySelector('.close').addEventListener('click', function() {
tag.remove();
updateMatchesVisibility(); // Check visibility when a chip is removed
});
}
function updateMatchesVisibility() {
const chipCount = tagsInputContainer.getElementsByClassName('tag').length;
console.log('Chip count:', chipCount); // Debugging message
if (chipCount > 0) {
mainContainer.classList.add('show-matches');
} else {
mainContainer.classList.remove('show-matches');
}
}
</script>
<script src="/static/theme.js"></script>
</body>
</html>

491
main.go
View File

@ -2,12 +2,10 @@ package main
import (
"database/sql"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"strings"
_ "github.com/mattn/go-sqlite3"
)
@ -16,356 +14,225 @@ type Question struct {
ID int
QuestionText string
QuestionType string
Options []string
}
type Match struct {
Name string `json:"name"`
Email string `json:"email"`
Classes []string `json:"classes"`
}
var db *sql.DB
func renderForm(w http.ResponseWriter, r *http.Request) {
log.Println("[DEBUG] Entering renderForm handler")
tmpl := template.Must(template.ParseFiles("index.html"))
log.Println("[DEBUG] Rendering template with questions")
err := tmpl.Execute(w, nil)
rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
if err != nil {
log.Println("[ERROR] Error rendering template:", err)
}
}
func handleFormSubmit(w http.ResponseWriter, r *http.Request) {
log.Println("[DEBUG] Entering handleFormSubmit handler")
// Parse the form data
if err := r.ParseForm(); err != nil {
http.Error(w, "Invalid form data", http.StatusBadRequest)
return
}
// Log all form data
log.Printf("[DEBUG] Form data: %v\n", r.Form)
name := r.FormValue("name")
email := r.FormValue("email")
phone := r.FormValue("phone")
dorm := r.FormValue("dorm")
classes := r.FormValue("classes")
// Insert the user data into the database
tx, err := db.Begin()
if err != nil {
log.Fatalf("[ERROR] Failed to begin transaction: %v\n", err)
return
}
result, err := tx.Exec(`INSERT INTO users (name, email, phone, dorm) VALUES (?, ?, ?, ?)`, name, email, phone, dorm)
if err != nil {
tx.Rollback()
log.Fatalf("[ERROR] Failed to insert user: %v\n", err)
return
}
userID, err := result.LastInsertId()
if err != nil {
tx.Rollback()
log.Fatalf("[ERROR] Failed to get last insert ID: %v\n", err)
return
}
// Insert the classes associated with this user
classList := strings.Split(classes, ",")
for _, class := range classList {
log.Printf("[DEBUG] Proccessing class: %s\n", class)
_, err := tx.Exec(`INSERT INTO classes (user_id, class_name) VALUES (?, ?)`, userID, strings.TrimSpace(class))
if err != nil {
tx.Rollback()
log.Fatalf("[ERROR] Failed to insert class: %v\n", err)
return
}
}
err = tx.Commit()
if err != nil {
log.Fatalf("[ERROR] Failed to commit transaction: %v\n", err)
return
}
log.Println("[DEBUG] User and classes inserted successfully")
fmt.Fprintf(w, "Thank you for your submission!")
log.Println("[DEBUG] Exiting handleFormSubmit handler")
}
// func handleAdmin(w http.ResponseWriter, r *http.Request) {
// log.Println("[DEBUG] Entering handleAdmin handler")
// rows, err := db.Query(`
// SELECT q.question_text, a.answer, GROUP_CONCAT(t.tag, ', ') as tags
// FROM answers a
// JOIN questions q ON a.question_id = q.id
// LEFT JOIN tags t ON a.id = t.answer_id
// GROUP BY a.id
// ORDER BY q.question_order
// `)
// if err != nil {
// log.Printf("[ERROR] Error fetching submissions: %v\n", err)
// http.Error(w, "Failed to fetch submissions", http.StatusInternalServerError)
// return
// }
// defer rows.Close()
//
// var submissions []struct {
// QuestionText string
// Answer string
// Tags string
// }
// log.Println("[DEBUG] Fetching submissions")
//
// for rows.Next() {
// var submission struct {
// QuestionText string
// Answer string
// Tags string
// }
// err := rows.Scan(&submission.QuestionText, &submission.Answer, &submission.Tags)
// if err != nil {
// log.Printf("[ERROR] Error scanning submission: %v\n", err)
// continue
// }
// submissions = append(submissions, submission)
// log.Printf("[DEBUG] Fetched submission: %v\n", submission)
// }
//
// tmpl := template.Must(template.ParseFiles("admin.html"))
// log.Println("[DEBUG] Rendering admin template with submissions")
// tmpl.Execute(w, submissions)
// }
//
// func handleAddQuestion(w http.ResponseWriter, r *http.Request) {
// log.Println("[DEBUG] Entering handleAddQuestion handler")
// if r.Method != http.MethodPost {
// log.Printf("[ERROR] Invalid request method: %v\n", r.Method)
// http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
// return
// }
//
// r.ParseForm()
// questionText := r.FormValue("question_text")
// questionType := r.FormValue("question_type")
// options := r.FormValue("options")
//
// log.Printf("[DEBUG] Adding new question: %s, Type: %s\n", questionText, questionType)
//
// insertQuery := `INSERT INTO questions (question_text, question_type, question_order) VALUES (?, ?, (SELECT IFNULL(MAX(question_order), 0) + 1 FROM questions));`
// res, err := db.Exec(insertQuery, questionText, questionType)
// if err != nil {
// log.Printf("[ERROR] Error adding new question: %v\n", err)
// http.Error(w, "Failed to add question", http.StatusInternalServerError)
// return
// }
//
// if questionType == "multiple_choice" || questionType == "single_choice" || questionType == "dropdown" || questionType == "tags" {
// questionID, err := res.LastInsertId()
// if err != nil {
// log.Printf("[ERROR] Error getting question ID: %v\n", err)
// http.Error(w, "Failed to add question", http.StatusInternalServerError)
// return
// }
// log.Printf("[DEBUG] Inserted question ID: %d\n", questionID)
//
// optionsList := strings.Split(options, ",")
// for _, option := range optionsList {
// option = strings.TrimSpace(option)
// log.Printf("[DEBUG] Adding option: %s to question ID: %d\n", option, questionID)
// _, err = db.Exec("INSERT INTO question_options (question_id, option_text) VALUES (?, ?)", questionID, option)
// if err != nil {
// log.Printf("[ERROR] Error adding options: %v\n", err)
// http.Error(w, "Failed to add options", http.StatusInternalServerError)
// return
// }
// }
// }
//
// log.Println("[DEBUG] Redirecting to manage page")
// http.Redirect(w, r, "/manage", http.StatusSeeOther)
// }
//
// func handleManage(w http.ResponseWriter, r *http.Request) {
// log.Println("[DEBUG] Entering handleManage handler")
// rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
// if err != nil {
// log.Printf("[ERROR] Error fetching questions: %v\n", err)
// http.Error(w, "Failed to fetch questions", http.StatusInternalServerError)
// return
// }
// defer rows.Close()
//
// var questions []Question
// log.Println("[DEBUG] Fetching questions for management")
//
// for rows.Next() {
// var question Question
// err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
// if err != nil {
// log.Printf("[ERROR] Error scanning question: %v\n", err)
// continue
// }
// log.Printf("[DEBUG] Found question: %v\n", question)
//
// if question.QuestionType == "multiple_choice" || question.QuestionType == "single_choice" || question.QuestionType == "dropdown" {
// optionRows, err := db.Query("SELECT option_text FROM question_options WHERE question_id = ?", question.ID)
// if err != nil {
// log.Printf("[ERROR] Error fetching options: %v\n", err)
// continue
// }
// defer optionRows.Close()
//
// for optionRows.Next() {
// var optionText string
// err = optionRows.Scan(&optionText)
// if err != nil {
// log.Printf("[ERROR] Error scanning option: %v\n", err)
// continue
// }
// question.Options = append(question.Options, optionText)
// log.Printf("[DEBUG] Found option: %s\n", optionText)
// }
// }
//
// questions = append(questions, question)
// }
//
// tmpl := template.Must(template.ParseFiles("manage.html"))
// log.Println("[DEBUG] Rendering manage template with questions")
// tmpl.Execute(w, questions)
// }
//
// func handleRemoveQuestion(w http.ResponseWriter, r *http.Request) {
// log.Println("[DEBUG] Entering handleRemoveQuestion handler")
// questionID := r.URL.Query().Get("id")
// log.Printf("[DEBUG] Removing question with ID: %s\n", questionID)
//
// deleteQuery := `DELETE FROM questions WHERE id = ?`
// _, err := db.Exec(deleteQuery, questionID)
// if err != nil {
// log.Printf("[ERROR] Error removing question: %v\n", err)
// http.Error(w, "Failed to remove question", http.StatusInternalServerError)
// return
// }
//
// log.Println("[DEBUG] Redirecting to manage page after deletion")
// http.Redirect(w, r, "/manage", http.StatusSeeOther)
// }
func handleFetchMatches(w http.ResponseWriter, r *http.Request) {
log.Println("[DEBUG] Entering handleFetchMatches handler")
// Parse JSON request body
var requestData struct {
Classes []string `json:"classes"`
}
err := json.NewDecoder(r.Body).Decode(&requestData)
if err != nil || len(requestData.Classes) == 0 {
http.Error(w, "Invalid request data", http.StatusBadRequest)
return
}
log.Printf("[DEBUG] Classes to match: %v\n", requestData.Classes)
// Prepare query to fetch users who share any of the classes
query := `
SELECT u.name, u.email, GROUP_CONCAT(c.class_name) as class_list
FROM users u
JOIN classes c ON u.id = c.user_id
GROUP BY u.id
HAVING COUNT(CASE WHEN c.class_name IN (` + strings.Trim(strings.Repeat("?,", len(requestData.Classes)), ",") + `) THEN 1 END) > 0;
`
// Prepare the arguments for the query
args := make([]interface{}, len(requestData.Classes))
for i, class := range requestData.Classes {
args[i] = strings.TrimSpace(class)
}
// Execute the query
rows, err := db.Query(query, args...)
if err != nil {
log.Printf("[ERROR] Failed to fetch matching users: %v\n", err)
http.Error(w, "Failed to fetch matches", http.StatusInternalServerError)
log.Printf("Error fetching questions: %v\n", err)
http.Error(w, "Failed to fetch form questions", http.StatusInternalServerError)
return
}
defer rows.Close()
var matches []Match
var questions []Question
for rows.Next() {
var match Match
var classList string
if err := rows.Scan(&match.Name, &match.Email, &classList); err != nil {
log.Printf("[ERROR] Failed to scan result row: %v\n", err)
var question Question
err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
if err != nil {
log.Printf("Error scanning question: %v\n", err)
continue
}
match.Classes = strings.Split(classList, ",")
matches = append(matches, match)
questions = append(questions, question)
}
// Return matches as JSON, always return an array, even if empty
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(matches); err != nil {
log.Printf("[ERROR] Failed to encode matches: %v\n", err)
http.Error(w, "Failed to return matches", http.StatusInternalServerError)
tmpl := template.Must(template.ParseFiles("index.html"))
tmpl.Execute(w, questions)
}
func handleFormSubmit(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
log.Println("[DEBUG] Exiting handleFetchMatches handler")
err := r.ParseForm()
if err != nil {
log.Printf("Error parsing form: %v\n", err)
http.Error(w, "Failed to parse form", http.StatusInternalServerError)
return
}
insertAnswerQuery := `INSERT INTO answers (question_id, answer) VALUES (?, ?)`
tx, err := db.Begin()
if err != nil {
log.Printf("Error starting transaction: %v\n", err)
http.Error(w, "Failed to save answers", http.StatusInternalServerError)
return
}
defer func() {
if err != nil {
tx.Rollback()
} else {
tx.Commit()
}
}()
for key, values := range r.Form {
if len(key) > 7 && key[:7] == "custom_" {
var questionID int
_, err := fmt.Sscanf(key, "custom_%d", &questionID)
if err != nil {
log.Printf("Error parsing question ID from field name %s: %v\n", key, err)
continue
}
answer := values[0]
_, err = tx.Exec(insertAnswerQuery, questionID, answer)
if err != nil {
log.Printf("Error saving answer for question %d: %v\n", questionID, err)
http.Error(w, "Failed to save answers", http.StatusInternalServerError)
return
}
}
}
fmt.Fprintf(w, "Thank you for your submission!")
}
func handleAdmin(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query(`
SELECT q.question_text, a.answer
FROM answers a
JOIN questions q ON a.question_id = q.id
ORDER BY q.question_order
`)
if err != nil {
log.Printf("Error fetching submissions: %v\n", err)
http.Error(w, "Failed to fetch submissions", http.StatusInternalServerError)
return
}
defer rows.Close()
var submissions []struct {
QuestionText string
Answer string
}
for rows.Next() {
var submission struct {
QuestionText string
Answer string
}
err := rows.Scan(&submission.QuestionText, &submission.Answer)
if err != nil {
log.Printf("Error scanning submission: %v\n", err)
continue
}
submissions = append(submissions, submission)
}
tmpl := template.Must(template.ParseFiles("admin.html"))
tmpl.Execute(w, submissions)
}
func handleAddQuestion(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
r.ParseForm()
questionText := r.FormValue("question_text")
insertQuery := `INSERT INTO questions (question_text, question_type, question_order) VALUES (?, 'text', (SELECT IFNULL(MAX(question_order), 0) + 1 FROM questions));`
_, err := db.Exec(insertQuery, questionText)
if err != nil {
log.Printf("Error adding new question: %v\n", err)
http.Error(w, "Failed to add question", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/manage", http.StatusSeeOther)
}
func handleManage(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT id, question_text, question_type FROM questions ORDER BY question_order")
if err != nil {
log.Printf("Error fetching questions: %v\n", err)
http.Error(w, "Failed to fetch questions", http.StatusInternalServerError)
return
}
defer rows.Close()
var questions []Question
for rows.Next() {
var question Question
err := rows.Scan(&question.ID, &question.QuestionText, &question.QuestionType)
if err != nil {
log.Printf("Error scanning question: %v\n", err)
continue
}
questions = append(questions, question)
}
if len(questions) == 0 {
log.Println("No questions found")
}
tmpl := template.Must(template.ParseFiles("manage.html"))
err = tmpl.Execute(w, questions)
if err != nil {
log.Printf("Error executing template: %v\n", err)
}
}
func handleRemoveQuestion(w http.ResponseWriter, r *http.Request) {
questionID := r.URL.Query().Get("id")
deleteQuery := `DELETE FROM questions WHERE id = ?`
_, err := db.Exec(deleteQuery, questionID)
if err != nil {
log.Printf("Error removing question: %v\n", err)
http.Error(w, "Failed to remove question", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/manage", http.StatusSeeOther)
}
func main() {
var err error
log.Println("[DEBUG] Opening database connection")
db, err = sql.Open("sqlite3", "./formdata.db")
// db, err = sql.Open("sqlite3", ":memory:")
if err != nil {
log.Fatal(err)
}
createTableQueries := `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
phone TEXT,
dorm TEXT
);
CREATE TABLE IF NOT EXISTS questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_text TEXT NOT NULL,
question_type TEXT NOT NULL DEFAULT 'text',
question_order INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS classes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
class_name TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
`
CREATE TABLE IF NOT EXISTS answers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
question_id INTEGER NOT NULL,
answer TEXT NOT NULL,
FOREIGN KEY (question_id) REFERENCES questions(id)
);
`
log.Println("[DEBUG] Creating database tables")
_, err = db.Exec(createTableQueries)
if err != nil {
log.Fatalf("[ERROR] Error creating tables: %v\n", err)
log.Fatal(err)
return
}
log.Println("[DEBUG] Database tables created successfully")
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
http.HandleFunc("/", renderForm)
http.HandleFunc("/submit", handleFormSubmit)
// http.HandleFunc("/admin", handleAdmin)
// http.HandleFunc("/manage", handleManage)
// http.HandleFunc("/manage/add", handleAddQuestion)
// http.HandleFunc("/manage/remove", handleRemoveQuestion)
http.HandleFunc("/fetch-matches", handleFetchMatches)
http.HandleFunc("/admin", handleAdmin)
http.HandleFunc("/manage", handleManage)
http.HandleFunc("/manage/add", handleAddQuestion)
http.HandleFunc("/manage/remove", handleRemoveQuestion)
log.Println("[DEBUG] Server starting at :8080")
fmt.Println("Server started at :8080")
log.Fatal(http.ListenAndServe(":8080", nil))

View File

@ -10,63 +10,35 @@
<div class="container">
<h1>Manage Questions</h1>
<!-- Dark mode toggle button -->
<button id="dark-mode-toggle">Toggle Dark Mode</button>
<h2>Add New Question</h2>
<form action="/manage/add" method="post">
<label for="question_text">Question Text:</label>
<input type="text" id="question_text" name="question_text" required><br><br>
<label for="question_type">Question Type:</label>
<select id="question_type" name="question_type" required>
<option value="text">Text</option>
<option value="multiple_choice">Multiple Choice</option>
<option value="single_choice">Single Choice</option>
<option value="dropdown">Dropdown</option>
<option value="tags">Tag Input</option>
</select><br><br>
<div id="options-section" style="display:none;">
<label for="options">Options (comma-separated):</label>
<textarea id="options" name="options" rows="4" cols="50"></textarea><br><br>
</div>
<input type="submit" value="Add Question">
</form>
<h2>Existing Questions</h2>
<table>
<tr>
<th>Question Text</th>
<th>Question Type</th>
<th>Options</th>
<th>Actions</th>
</tr>
{{range .}}
<tr>
<td>{{.QuestionText}}</td>
<td>{{.QuestionType}}</td>
<td>
{{if .Options}}
<ul>
{{range .Options}}
<li>{{.}}</li>
{{end}}
</ul>
{{else}}None{{end}}
</td>
<td><a href="/manage/remove?id={{.ID}}">Remove</a></td>
</tr>
{{else}}
<tr>
<td colspan="4">No questions found.</td>
<td colspan="2">No questions found.</td>
</tr>
{{end}}
</table>
</div>
<script src="/static/theme.js"></script>
<script src="/static/util.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 B

View File

@ -1,151 +1,147 @@
/* General Styles */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f9f9fc;
font-family: Arial, sans-serif;
background-color: #f4f4f9;
color: #333;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
/* Main container for both form and matches */
.container {
display: flex;
justify-content: center;
align-items: center;
width: 90%; /* Increased width for a more uniform layout */
max-width: 1000px;
transition: all 0.6s ease-in-out;
}
/* Form and Matches Sections */
.form-section, .matches-section {
width: 100%;
width: 80%;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transition: all 0.6s ease-in-out;
box-sizing: border-box; /* Ensure padding is included in the width */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
margin-top: 50px;
border-radius: 10px;
}
/* Ensure both form and matches have the same width */
.form-section {
max-width: 400px;
h1 {
text-align: center;
color: #333; /* Default color for light mode */
}
/* Hidden matches section by default */
.matches-section {
opacity: 0;
height: 0;
visibility: hidden;
transition: all 0.6s ease-in-out; /* Smooth transition for visibility */
form {
margin: 20px 0;
}
/* Show matches when class chip is added */
.show-matches .matches-section {
opacity: 1;
height: auto;
visibility: visible;
margin-left: 20px;
transition: all 0.6s ease-in-out;
form label {
display: block;
margin-bottom: 10px;
font-weight: bold;
color: #555;
}
/* Smooth fade-in animation for the matches section */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(50px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Form Section Animation */
.show-matches .form-section {
transform: translateX(-50%);
transition: all 0.6s ease-in-out;
}
/* Input fields */
form input[type="text"], form input[type="email"], form input[type="submit"] {
form input[type="text"], form input[type="email"], form textarea {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border-radius: 8px;
border: 1px solid #ddd;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 16px;
box-sizing: border-box;
background-color: white;
color: #333;
}
/* Submit button */
form input[type="submit"] {
background-color: #007bff;
background-color: #28a745;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
font-size: 16px;
}
form input[type="submit"]:hover {
background-color: #0056b3;
background-color: #218838;
}
/* Tags (Class Chips) Styling */
.tags-input-container {
display: flex;
flex-wrap: wrap;
border: 1px solid #ccc;
padding: 8px;
border-radius: 8px;
min-height: 40px;
cursor: text;
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.tags-input-container:focus-within {
border-color: #007bff;
table, th, td {
border: 1px solid #ddd;
}
.tag {
display: inline-flex;
align-items: center;
background-color: #e0e0e0;
border-radius: 20px;
padding: 4px 8px;
margin: 4px;
font-size: 14px;
th, td {
padding: 10px;
text-align: left;
}
.tag:hover .close {
display: inline-block;
th {
background-color: #f2f2f2;
color: #333;
}
.tag .close {
margin-left: 8px;
cursor: pointer;
display: none;
font-size: 14px;
color: red;
td {
background-color: white;
color: #333;
}
/* Fix for text overflow in input fields */
.tags-input-container input {
border: none;
outline: none;
flex-grow: 1;
font-size: 14px;
padding: 4px;
min-width: 50px;
a {
color: #007bff;
text-decoration: none;
}
/* Matches section styling (not a card for the 'No similar classes' text) */
#matches-container p {
margin: 0;
color: #666;
text-align: center;
a:hover {
text-decoration: underline;
}
/*
* DARK MODE
*/
body.dark-mode {
background-color: #1a1a1a;
color: #f4f4f9;
}
.container.dark-mode {
background-color: #333;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
}
h1.dark-mode {
color: #f4f4f9; /* Ensure that the heading is white and visible */
}
form label.dark-mode {
color: #f4f4f9;
}
form input[type="text"].dark-mode, form input[type="email"].dark-mode, form textarea.dark-mode {
background-color: #555;
color: #f4f4f9;
border: 1px solid #888;
}
form input[type="submit"].dark-mode {
background-color: #28a745;
color: white;
}
table.dark-mode {
background-color: #444;
}
th.dark-mode {
background-color: #555;
color: #f4f4f9;
}
td.dark-mode {
background-color: #666;
color: #f4f4f9;
}
a.dark-mode {
color: #66b3ff;
}
a.dark-mode:hover {
color: #3399ff;
text-decoration: underline;
}

View File

@ -1,177 +0,0 @@
console.log("entered script");
window.addEventListener('load', function () {
console.log("All resources finished loading!");
const questionTypeElement = document.getElementById('question_type');
if (questionTypeElement) {
questionTypeElement.addEventListener('change', function () {
const optionsSection = document.getElementById('options-section');
const selectedType = this.value;
if (selectedType === 'multiple_choice' || selectedType === 'single_choice' || selectedType === 'dropdown') {
optionsSection.style.display = 'block';
} else {
optionsSection.style.display = 'none';
}
});
}
document.querySelectorAll('input[id^="other_"]').forEach(el => {
const id = el.id.split('_')[1];
const otherTextElement = document.getElementById(`other_text_${id}`);
if (otherTextElement) {
el.addEventListener('change', function () {
otherTextElement.style.display = this.checked ? 'block' : 'none';
});
}
});
document.querySelectorAll('input[id^="radio_other_"]').forEach(el => {
const id = el.id.split('_')[2];
const radioOtherTextElement = document.getElementById(`radio_other_text_${id}`);
if (radioOtherTextElement) {
el.addEventListener('change', function () {
radioOtherTextElement.style.display = this.checked ? 'block' : 'none';
});
}
});
});
window.addEventListener('load', function () {
const tagsInput = document.getElementById('classes-input');
const matchesContainer = document.getElementById('matches-container');
let classes = []; // Stores the classes entered by the user
let fetchedPeople = []; // Stores the people returned from the server
function addClass(classname) {
const container = document.getElementById('classes-container');
const classElement = document.createElement('span');
classElement.classList.add('tag');
classElement.textContent = classname;
const closeIcon = document.createElement('span');
closeIcon.classList.add('close');
closeIcon.textContent = 'x';
closeIcon.addEventListener('click', function () {
container.removeChild(classElement);
classes = classes.filter(c => c !== classname);
fetchMatchingPeople(); // Fetch updated matches when a class is removed
});
classElement.appendChild(closeIcon);
container.appendChild(classElement);
}
function fetchMatchingPeople() {
if (classes.length === 0) {
matchesContainer.innerHTML = '<p>No one has a similar class yet!</p>';
return;
}
fetch("/fetch-matches", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ classes: classes })
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not OK");
}
return response.json();
})
.then(data => {
// Handle null or empty data
if (!data || data.length === 0) {
matchesContainer.innerHTML = '<p>No one has that class yet!</p>';
return;
}
fetchedPeople = data; // Store the fetched people
matchesContainer.innerHTML = ''; // Clear the matches container
renderPeople(fetchedPeople, classes); // Render matches
})
.catch(error => {
console.error("Error fetching matches:", error);
matchesContainer.innerHTML = '<p>No one has that class yet!</p>'; // Fallback message in case of any error
});
}
function renderPeople(people, filterClasses = []) {
matchesContainer.innerHTML = ''; // Clear current people list
people.forEach(person => {
const personDiv = document.createElement('div');
personDiv.classList.add('person');
// Render all classes: shared ones in bold, others in gray
let classesHtml = person.classes.map(classname => {
// Check if the classname is in the currently selected filters
if (filterClasses.includes(classname.trim())) {
// Shared classes: bold
return `<span class="class-shared">${classname}</span>`;
} else {
// Non-shared classes: gray out
return `<span class="class-other">${classname}</span>`;
}
}).join(', ');
// Display the person's name, email, and full class list
personDiv.innerHTML = `<strong>${person.name}</strong><br>Classes: ${classesHtml}<br>Email: ${person.email || 'N/A'}`;
matchesContainer.appendChild(personDiv);
});
}
function renderClassFilters() {
const filterContainer = document.getElementById('class-filter-container');
filterContainer.innerHTML = ''; // Clear existing filters
classes.forEach(classname => {
const filterLabel = document.createElement('label');
const filterCheckbox = document.createElement('input');
filterCheckbox.type = 'checkbox';
filterCheckbox.value = classname;
filterCheckbox.addEventListener('change', function () {
applyClassFilter(); // Apply filter when a checkbox is checked/unchecked
});
filterLabel.appendChild(filterCheckbox);
filterLabel.appendChild(document.createTextNode(classname));
filterContainer.appendChild(filterLabel);
});
}
function applyClassFilter() {
// Get the list of currently checked classes from the checkboxes
const selectedClasses = Array.from(document.querySelectorAll('#class-filter-container input:checked'))
.map(input => input.value);
// If no checkboxes are selected, use all classes (to keep shared ones bold)
const filterClasses = selectedClasses.length === 0 ? classes : selectedClasses;
// Filter people by selected classes, or show all people if no classes are checked
const filteredPeople = fetchedPeople.filter(person =>
selectedClasses.length === 0 || selectedClasses.some(cls => person.classes.includes(cls))
);
// Render the people with the selected filters and apply the shared class highlighting logic
renderPeople(filteredPeople, filterClasses);
}
tagsInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ',') {
event.preventDefault();
let value = tagsInput.value.trim().replace(/,$/, '');
if (value && !classes.includes(value)) { // Check for empty and duplicate values
addClass(value);
classes.push(value);
renderClassFilters(); // Update the class filters UI
fetchMatchingPeople(); // Fetch updated matches when a class is added
tagsInput.value = ''; // Clear the input
}
}
});
});