Skip to content
This repository was archived by the owner on Sep 26, 2025. It is now read-only.
Merged
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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Changelog
All notable changes to this project will be documented in this file. The documented versioning starts from the public alpha release `0.3.1`.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).


## [Unreleased] - YYYY-MM-DD

### Public

#### Added
- Added link to the extension in Chrome store
- Added home button to HR Zones config display
- Added button routing to Strava calendar on the home screen
- Added button routing to HR Zones config on the home screen
- Added info panel displaying the initial sync status
- Enabled account deletion

#### Changed
- Changed time in zone calculation to be based on moving time instead of elapsed time

####
- ...


### Internal

#### Added
- Added support for separate development Chrome extension ID

#### Changed
- ...
#### Fixed
- Used `gunicorn.conf.py` to run APScheduler in a single process only
91 changes: 91 additions & 0 deletions backend/api/templates/api/changelog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zonelens Changelog</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #fdfdfd;
}
h1, h2, h3 {
color: #2c3e50;
}
h1 {
border-bottom: 2px solid #ecf0f1;
padding-bottom: 10px;
}
.version-info {
background-color: #e8f6f3;
border-left: 5px solid #1abc9c;
padding: 15px;
margin: 20px 0;
border-radius: 5px;
}
ul {
list-style-type: '✅ ';
padding-left: 20px;
}
li {
margin-bottom: 10px;
}
.category {
font-weight: bold;
color: #FC4C02;
}
.home-button-container {
text-align: center;
margin-top: 30px;
padding-bottom: 20px;
}
.home-button {
background-color: #aaaaaa;
color: #ffffff;
border: 1px solid #999999;
padding: 8px 15px;
font-size: 0.9em;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
transition: opacity 0.2s;
}
.home-button:hover {
opacity: 0.85;
}
</style>
</head>
<body>
<h1>Zonelens Changelog</h1>
<div class="version-info">
<p>Welcome to version <strong>0.4.0</strong>! We've made some exciting updates since version 0.3.1. Here’s what’s new.</p>
</div>

<h2>Public Changes</h2>

<h3><span class="category">Added</span></h3>
<ul>
<li>Link to the extension in Chrome store</li>
<li>Home button to HR Zones config display</li>
<li>Button routing to Strava calendar on the home screen</li>
<li>Button routing to HR Zones config on the home screen</li>
<li>Info panel displaying the initial sync status</li>
<li>Enabled account deletion</li>
</ul>

<h3><span class="category">Changed</span></h3>
<ul>
<li>Time in zone calculation is now based on moving time instead of elapsed time</li>
</ul>

<div class="home-button-container">
<a href="{% url 'index' %}" class="home-button">Home</a>
</div>

</body>
</html>
128 changes: 45 additions & 83 deletions backend/api/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,32 @@
.extension-link a:hover {
text-decoration: underline;
}
.delete-account-container {
.changelog-button-container {
position: fixed;
bottom: 15px;
left: 15px;
z-index: 1000;
}
.changelog-button {
background-color: #aaaaaa;
color: #ffffff;
border: 1px solid #999999;
padding: 8px 15px;
font-size: 0.9em;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
transition: opacity 0.2s;
}
.changelog-button:hover {
opacity: 0.85;
}
.delete-account-container {
position: fixed;
bottom: 15px;
left: 125px;
z-index: 1000;
}
.delete-button {
background-color: #aaaaaa;
color: #ffffff;
Expand All @@ -148,6 +168,9 @@
border-radius: 5px;
cursor: pointer;
}
.delete-button:hover {
opacity: 0.85;
}
/* Modal Styles */
.modal {
display: none; /* Hidden by default */
Expand Down Expand Up @@ -201,18 +224,13 @@
<body>
<div class="container">
<h1>Welcome to ZoneLens!</h1>
<div id="auth-section">
<p>Customize your Strava activity heart rate and power zones with ease. Connect your Strava account to get started.</p>
<a href="/api/auth/strava" class="strava-connect-link">
<img src="{% static 'images/btn_strava_connect_with_orange_x2.png' %}" alt="Login with Strava" class="strava-connect-img">
</a>

<p class="extension-link">Get the <a href="https://chrome.google.com/webstore/detail/zonelens/iaohacnoldkcffapjbbjfifmamcnedkk" target="_blank" rel="noopener noreferrer">ZoneLens Chrome Extension</a>.</p>
</div>

<div id="profile" style="display: none;">
{% if user.is_authenticated %}
<div id="profile">
<h2>Connection Status</h2>
<p id="connection-status-message" style="text-align: center; font-size: 1.2em; margin-top: 20px; margin-bottom: 20px; color: #333;"></p>
<p id="connection-status-message" style="text-align: center; font-size: 1.2em; margin-top: 20px; margin-bottom: 20px; color: #333;">
Hi {{ user.first_name | default:'there' }}! You're connected. <span style="font-size: 1.5em;">🎉</span>
</p>
{% if total_activities is not None %}
<div style="text-align: center; font-size: 0.9em; margin-bottom: 15px; padding: 10px; border: 1px solid #bde5f8; background-color: #f0f8ff; color: #00529b; border-radius: 5px;">
Initial activity sync: <strong>{{ num_processed }}/{{ total_activities }}</strong> activities synced.
Expand All @@ -227,17 +245,26 @@ <h2>Connection Status</h2>
<a href="{% url 'user_hr_zones_display' %}" class="basic-button strava-button-override">My HR Zones</a>
</div>
</div>
{% else %}
<div id="auth-section">
<p>Customize your Strava activity heart rate and power zones with ease. Connect your Strava account to get started.</p>
<a href="{% url 'strava_authorize' %}" class="strava-connect-link">
<img src="{% static 'images/btn_strava_connect_with_orange_x2.png' %}" alt="Login with Strava" class="strava-connect-img">
</a>

<div id="error" style="display: none;">
<h2>Error</h2>
<p id="error-message"></p>
<p class="extension-link">Get the <a href="https://chrome.google.com/webstore/detail/zonelens/iaohacnoldkcffapjbbjfifmamcnedkk" target="_blank" rel="noopener noreferrer">ZoneLens Chrome Extension</a>.</p>
</div>
{% endif %}
</div>

<div class="strava-logo-container">
<a href="https://strava.com" target="_blank" rel="noopener noreferrer"><img src="{% static 'images/api_logo_pwrdBy_strava_stack_orange.png' %}" alt="Powered by Strava"></a>
</div>

<div class="changelog-button-container">
<button class="changelog-button" onclick="window.location.href='{% url 'changelog' %}'">Changelog</button>
</div>

{% if user.is_authenticated %}
<div class="delete-account-container">
<button class="delete-button">Delete account</button>
Expand Down Expand Up @@ -322,82 +349,17 @@ <h2>Error</h2>
<p class="author-credit">Authored by stancld</p>

<script>
function logout() {
window.location.href = "{% url 'logout' %}";
}

function openStravaCalendar() {
const monthShortNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const now = new Date();
const monthShortName = monthShortNames[now.getMonth()]; // Get current month's short name
const stravaCalendarUrl = `https://www.strava.com/athlete/calendar#${monthShortName}`;
window.open(stravaCalendarUrl, '_blank'); // Open in a new tab
}

const authSection = document.getElementById('auth-section');
const profileSection = document.getElementById('profile');
const connectionStatusMessage = document.getElementById('connection-status-message'); // Changed from profileData
const errorSection = document.getElementById('error');
const errorMessage = document.getElementById('error-message');

// Function to fetch profile data using the stored token
async function fetchProfile(token) {
try {
const response = await fetch('/api/profile/', {
headers: {
'Authorization': `Token ${token}`,
'Accept': 'application/json'
}
});

if (response.ok) {
const data = await response.json();
if (data.first_name) {
connectionStatusMessage.innerHTML = `Hi ${data.first_name}! You're connected. <span style="font-size: 1.5em;">🎉</span>`;
} else {
connectionStatusMessage.innerHTML = `Successfully connected to Strava! <span style="font-size: 1.5em;">🎉</span>`;
}
authSection.style.display = 'none';
profileSection.style.display = 'block';
errorSection.style.display = 'none';
} else {
// Handle non-OK responses (e.g., 401 Unauthorized, 403 Forbidden)
const errorText = await response.text();
console.error('Failed to fetch profile:', response.status, errorText);
showError(`Failed to fetch profile: ${response.status}. Check console for details.`);
logout(); // Clear token if it's invalid
}
} catch (error) {
console.error('Network error fetching profile:', error);
showError('Network error fetching profile. Is the backend server running?');
logout();
}
}

// Function to display errors
function showError(message) {
errorMessage.textContent = message;
errorSection.style.display = 'block';
profileSection.style.display = 'none';
authSection.style.display = 'block'; // Show login button again
}

// Function to logout (clear token and refresh)
function logout() {
localStorage.removeItem('authToken');
profileSection.style.display = 'none';
authSection.style.display = 'block';
errorSection.style.display = 'none';
// Optionally redirect or just update UI
// window.location.reload();
}

// Check for token on page load
window.onload = () => {
const token = localStorage.getItem('authToken');
if (token) {
fetchProfile(token);
} else {
authSection.style.display = 'block';
profileSection.style.display = 'none';
}
};
</script>

</body>
Expand Down
2 changes: 2 additions & 0 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
path("profile/sync_status", views.sync_status, name="sync_status"),
path("auth/strava/", views.strava_authorize, name="strava_authorize"),
path("auth/strava/callback/", views.strava_callback, name="strava_callback"),
path("auth/logout/", views.logout_view, name="logout"),
path(
"settings/custom-zones/<uuid:pk>/",
views.CustomZonesSettingsDetailView.as_view(),
Expand Down Expand Up @@ -67,4 +68,5 @@
views.StravaWebhookAPIView.as_view(),
name="strava_webhook",
),
path("changelog/", views.changelog_view, name="changelog"),
]
13 changes: 12 additions & 1 deletion backend/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import requests
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model, login
from django.contrib.auth import get_user_model, login, logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.http import (
Expand Down Expand Up @@ -213,6 +213,17 @@ def index_view(request: HttpRequest) -> HttpResponse:
return render(request, "index.html", context)


def changelog_view(request: Request) -> HttpResponse:
"""Renders the changelog page."""
return render(request, "api/changelog.html")


def logout_view(request: Request) -> HttpResponse:
"""Logs the user out."""
logout(request)
return HttpResponseRedirect("/")


class ProfileView(APIView):
permission_classes = [IsAuthenticated]

Expand Down
10 changes: 10 additions & 0 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const PRODUCTION_DOMAIN = 'https://strava-zones.com';
const DEVELOPMENT_DOMAIN = 'https://localhost:8000';
const IS_PRODUCTION_BUILD = false; // Set to true for production builds

chrome.runtime.onInstalled.addListener(function(details) {
if (details.reason === 'update') {
console.log(`Zonelens updated to ${chrome.runtime.getManifest().version}.`);
chrome.tabs.create({ url: `${IS_PRODUCTION_BUILD ? PRODUCTION_DOMAIN : DEVELOPMENT_DOMAIN}/api/changelog/` });
}
});
5 changes: 4 additions & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"permissions": [],
"host_permissions": [
"*://*.strava.com/*",
"https://localhost:8000/*",
"http://127.0.0.1:8000/*",
"https://strava-zones.com/*"
],
"content_scripts": [
Expand All @@ -25,6 +25,9 @@
"512": "images/icon512.png"
}
},
"background": {
"service_worker": "background.js"
},
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
Expand Down