Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 122 additions & 122 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,122 +1,122 @@
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.buildlog/
.history
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Coverage
coverage/
# Symbols
app.*.symbols
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock.venv/
# Python virtual environment
venv/
.venv/
# Miscellaneous
*.class
*.lock
*.log
*.pyc
*.swp
.buildlog/
.history



# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json

# packages file containing multi-root paths
.packages.generated

# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-preload-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds

# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks

# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*

# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/

# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake

# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake

# Coverage
coverage/

# Symbols
app.*.symbols

# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock.venv/
# Python virtual environment
venv/
.venv/
111 changes: 111 additions & 0 deletions static/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,70 @@ updateProfileWidgets();
return valid;
}



// ----------------------------------------------------------
// Form submission and API call
// ----------------------------------------------------------

form.addEventListener("submit", function (evt) {
evt.preventDefault(); //stop the browser from reloading the page on form submit
clearAllErrors();

if (skillsTextInput.value.trim()) {
addSkill(skillsTextInput.value);
skillsTextInput.value = "";
hideSuggestions();
}

if (!validateForm()) return; //stop - anything missing/invalid

setLoadingState(true);

// Allow browser to paint spinner before request starts
requestAnimationFrame(function () {

//combine form values into an object to send to server/api
var payload = {
// Prefer the hidden input value; fall back to raw text box if hidden input is empty
skills: skillsHidden.value.trim() || skillsTextInput.value.trim(),
level: document.getElementById("level").value,
interest: document.getElementById("interest").value,
time: document.getElementById("time").value
};

//post the data to backend api as JSON, then handle the response
fetch("/api/recommend", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) //convert object to json string
})
.then(function (res) {
return res.json(); //parse the response as JSON
})
.then(function (data) {
setLoadingState(false);

if (data.error) {
var generalErr = document.getElementById("form-error-general");
if (generalErr) generalErr.textContent = data.error;
return;
}

renderResults(data.projects || [], data.message);
})
.catch(function (err) {
// this runs if the network request itself fails
setLoadingState(false);
var generalErr = document.getElementById("form-error-general");
if (generalErr) generalErr.textContent = "Something went wrong. Please try again.";
console.error("API request failed:", err);
});
});
});


// Manages the loading state of the form and results section(whats visible or not)
function setLoadingState(isLoading) {
submitBtn.disabled = isLoading;
submitBtn.setAttribute("aria-busy", isLoading ? "true" : "false");
Expand All @@ -466,9 +530,56 @@ updateProfileWidgets();
resultsSection.scrollIntoView({ behavior: "smooth" });
} else {
resultsLoadingEl.style.display = "none";
resultsGrid.style.display = "grid"; //switch back to grid layout
}
}


// ----------------------------------------------------------
// Render result cards
// ----------------------------------------------------------

//takes the array of projects from the api and draws them on the page as cards
//if array is empty it shows the "no results" message instead
function renderResults(projects, message) {
resultsSection.style.display = "block";
resultsLoadingEl.style.display = "none";
// Clear out any cards from a previous search before showing new ones
resultsGrid.innerHTML = "";

if (!projects || projects.length === 0) { //if no projects returned from api, show the "no results" message and hide the grid
resultsGrid.style.display = "none";
resultsEmptyEl.style.display = "block";

var interestEl = document.getElementById("interest");
var selectedInterest = interestEl ? interestEl.value : null;

// Show a friendly custom message when the user selected an interest
if (emptyMessageEl) {
if (selectedInterest) {
emptyMessageEl.textContent = "No projects are currently available for this interest. Please check back later or try a different area.";
} else if (message) {
emptyMessageEl.textContent = message;
} else {
emptyMessageEl.textContent = "Try adjusting your skills or choosing a different interest area.";
}
}

resultsSection.scrollIntoView({ behavior: "smooth" });
return;
}

resultsEmptyEl.style.display = "none";
resultsGrid.style.display = "grid";

//build a card for each project and add it to the grid
projects.forEach(function (project) {
resultsGrid.appendChild(buildProjectCard(project));
});

resultsSection.scrollIntoView({ behavior: "smooth" });
}

function truncate(text, maxLength) {
text = text || "";
return text.length > maxLength ? text.slice(0, maxLength) + "..." : text;
Expand Down
Loading