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
55 changes: 55 additions & 0 deletions static/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,43 @@ if (isIndexPage) {

setLoadingState(true);

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

var payload = {
skills: skillsHidden.value.trim() || skillsTextInput.value.trim(),
level: document.getElementById("level").value,
interest: document.getElementById("interest").value,
time: document.getElementById("time").value
};

fetch("/api/recommend", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(function (res) {
return res.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 () {

setLoadingState(false);
//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
Expand All @@ -460,6 +497,15 @@ if (isIndexPage) {
.then(function (res) { return res.json(); }) //parse the response as JSON
.then(function (data) {
setLoadingState(false);

var generalErr = document.getElementById("form-error-general");

if (generalErr) {
generalErr.textContent =
"Something went wrong. Please try again.";
}
});
});
if (data.error) {
var generalErr = document.getElementById("form-error-general");
if (generalErr) generalErr.textContent = data.error;
Expand All @@ -480,6 +526,9 @@ if (isIndexPage) {
function setLoadingState(isLoading) {
// Disable the button so the user can't accidentally submit twice
submitBtn.disabled = isLoading;
submitBtn.setAttribute("aria-busy", isLoading);
btnLabel.style.display = isLoading ? "none" : "inline";
btnLoading.style.display = isLoading ? "inline-flex" : "none";
btnLabel.style.display = isLoading ? "none" : "inline";
btnLoading.style.display = isLoading ? "inline" : "none";

Expand Down Expand Up @@ -510,6 +559,12 @@ if (isIndexPage) {
// Clear out any cards from a previous search before showing new ones
resultsGrid.innerHTML = "";

if (!projects || projects.length === 0) {
resultsGrid.style.display = "none";
resultsEmptyEl.style.display = "block";
resultsGrid.style.display = "none";
resultsEmptyEl.style.display = "block";
if (message && emptyMessageEl) emptyMessageEl.textContent = message;
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";
Expand Down
73 changes: 73 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1958,6 +1958,79 @@ select:focus {
}
}


.btn-submit:disabled {
opacity: 0.75;
cursor: not-allowed;
transform: none;
}


.btn-loading-content {
display: inline-flex;
align-items: center;
gap: 10px;
justify-content: center;
}


.loading-spinner {
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #ffffff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}


.loading-box {
animation: fadeInUp 0.35s ease;
}

.loading-dots span {
animation: pulse 1.2s infinite ease-in-out;
}

.loading-dots span:nth-child(2) {
animation-delay: 0.2s;
}

.loading-dots span:nth-child(3) {
animation-delay: 0.4s;
}


@keyframes spin {
to {
transform: rotate(360deg);
}
}


@keyframes pulse {
0%, 80%, 100% {
transform: scale(0.7);
opacity: 0.5;
}

40% {
transform: scale(1);
opacity: 1;
}
}

@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}

to {
opacity: 1;
transform: translateY(0);
}
}
.tooltip {
display: inline-flex;
align-items: center;
Expand Down
59 changes: 58 additions & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<title>DevPath — Find Projects Based On Your Skills</title>
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/static/style.css" />
<link href="https://fonts.googleapis.com/css2?
family=Sora:wght@400;600;700;800&family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css2?family=Sora:wght@400;600;700;800&family=Inter:wght@400;500;600&display=swap"
rel="stylesheet" />
Expand Down Expand Up @@ -75,6 +77,9 @@ <h1 class="hero-heading">
<div class="hero-visual">
<div class="hero-visual-card hero-visual-card--top">
<div class="hvc-icon hvc-icon--blue">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6" />
<polyline points="8 6 2 12 8 18" /></svg>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6" />
Expand All @@ -95,6 +100,11 @@ <h1 class="hero-heading">
<span class="hvm-filename">expense_tracker.py</span>
</div>
<div class="hvm-code">
<span class="hvm-line"><span class="hvm-kw">def</span> <span class="hvm-fn">add_expense</span>(category, amount):</span>
<span class="hvm-line hvm-indent"><span class="hvm-cm"># Save entry to CSV</span></span>
<span class="hvm-line hvm-indent">date <span class="hvm-op">=</span> datetime.now()</span>
<span class="hvm-line hvm-indent"><span class="hvm-kw">with</span> open(DATA_FILE) <span
class="hvm-kw">as</span> f:</span>
<span class="hvm-line"><span class="hvm-kw">def</span> <span class="hvm-fn">add_expense</span>(category,
amount):</span>
<span class="hvm-line hvm-indent"><span class="hvm-cm"># Save entry to CSV</span></span>
Expand All @@ -111,10 +121,16 @@ <h1 class="hero-heading">

<div class="hero-visual-card hero-visual-card--bottom">
<div class="hvc-icon hvc-icon--green">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6
12 2 12" /></svg>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
</div>
<div class="hvc-text">
<span class="hvc-title">7-Step Roadmap</span>
Expand Down Expand Up @@ -244,6 +260,9 @@ <h3>Enter Your Skills</h3>
<p>Type your programming skills or click quick-select chips. Add as many as you like.</p>
</div>
<div class="step-connector">
<svg width="32" height="16" viewBox="0 0 32 16"><path d="M0 8 H28 M22 2 L30 8 L22 14"
stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<svg width="32" height="16" viewBox="0 0 32 16">
<path d="M0 8 H28 M22 2 L30 8 L22 14" stroke="currentColor" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round" />
Expand All @@ -255,6 +274,9 @@ <h3>Set Your Preferences</h3>
<p>Select your experience level, area of interest, and how much time you can commit.</p>
</div>
<div class="step-connector">
<svg width="32" height="16" viewBox="0 0 32 16"><path d="M0 8 H28 M22 2 L30 8 L22 14"
stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<svg width="32" height="16" viewBox="0 0 32 16">
<path d="M0 8 H28 M22 2 L30 8 L22 14" stroke="currentColor" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round" />
Expand Down Expand Up @@ -282,13 +304,21 @@ <h2 class="section-title">Everything You Need to Start Building</h2>

<div class="feature-card feature-card--pink">
<div class="feature-card-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0
0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0
0 1 0 7.75"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
<circle cx="9" cy="7" r="4" />
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
</div>
<h3>Personalized Matches</h3>
<p>Projects are scored against your exact skills, level, and interest — not pulled from a generic list.</p>
Expand All @@ -297,10 +327,16 @@ <h3>Personalized Matches</h3>

<div class="feature-card feature-card--yellow">
<div class="feature-card-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12
2 12"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
</svg>
</div>
<h3>Step-by-Step Roadmaps</h3>
<p>Each project includes a numbered roadmap so you always know what to build next, without guessing.</p>
Expand All @@ -309,6 +345,13 @@ <h3>Step-by-Step Roadmaps</h3>

<div class="feature-card feature-card--purple">
<div class="feature-card-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/></svg>
</div>
<h3>Starter Code Included</h3>
<p>Download a working template for every project. Skip the blank-page problem and start
building immediately.</p>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="16 18 22 12 16 6" />
Expand Down Expand Up @@ -360,8 +403,10 @@ <h2 class="section-title">Find Your Next Project</h2>
<input type="text" id="skills-input" placeholder="Type a skill and press Enter..." autocomplete="off" />
<input
type="text"
id="skills-input"
id="skills-input"
placeholder="Type a skill and press Enter..."
autocomplete="off"
/>
autocomplete="off"
aria-haspopup="listbox"
aria-expanded="false"
Expand Down Expand Up @@ -462,6 +507,15 @@ <h2 class="section-title">Find Your Next Project</h2>
<div class="form-error-msg" id="time-error"></div>
</div>

<!-- Submit button -->
<button type="submit" class="btn-submit" id="submit-btn">
<span id="btn-label">Generate My Projects</span>

<span id="btn-loading" class="btn-loading-content" style="display:none;">
<span class="loading-spinner"></span>
<span>Finding matches...</span>
</span>
</button>
<!-- General error (shown above the button) -->
<p class="form-error-general" id="form-error-general"></p>

Expand Down Expand Up @@ -498,6 +552,9 @@ <h2 class="section-title">Recommended Projects</h2>
<div id="results-empty" style="display:none;">
<div class="empty-state">
<div class="empty-icon">
<svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8" /><line
x1="21" y1="21" x2="16.65" y2="16.65" /></svg>
<svg width="52" height="52" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"
stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8" />
Expand Down
3 changes: 2 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ def test_download_code_found():
response = client.get("/project/1/download")
assert response.status_code == 200

def test_health_check(client):
def test_health_check():
client = get_client()
response = client.get("/health")
assert response.status_code == 200
data = response.get_json()
Expand Down
Loading