Skip to content
Draft
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
14 changes: 12 additions & 2 deletions app/controllers/plans_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class PlansController < ApplicationController
before_action :set_plan, only: %i[ show edit update destroy processing_status edit_workouts update_workouts create_blank_schedule ]
before_action :set_plan, only: %i[ show edit update destroy processing_status edit_workouts update_workouts create_blank_schedule enable_webhook_sync ]

# GET /plans or /plans.json
def index
Expand Down Expand Up @@ -104,6 +104,16 @@ def processing_status
end
end

# PATCH /plans/1/enable_webhook_sync
def enable_webhook_sync
if @plan.respond_to?(:webhook_enabled=)
@plan.update(webhook_enabled: true)
redirect_to plans_path, notice: "Strava activity sync has been enabled for this plan."
else
redirect_to plans_path, alert: "Webhook sync is not available yet. Database migration pending."
end
end

private
# Use callbacks to share common setup or constraints between actions.
def set_plan
Expand All @@ -112,7 +122,7 @@ def set_plan

# Only allow a list of trusted parameters through.
def plan_params
params.require(:plan).permit(:length, :race_date, :plan_type, photos: [])
params.require(:plan).permit(:length, :race_date, :plan_type, :webhook_enabled, photos: [])
end

# Create blank activities for a custom plan
Expand Down
8 changes: 8 additions & 0 deletions app/views/plans/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
<%= form.date_field :race_date, class: "form-input" %>
</div>

<div class="form-field">
<div class="flex items-center gap-3">
<%= form.check_box :webhook_enabled, class: "h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" %>
<%= form.label :webhook_enabled, "Enable Strava Activity Sync", class: "text-sm font-medium text-slate-700 cursor-pointer" %>
</div>
<p class="text-xs text-slate-500 mt-1">Automatically sync new Strava activities and match them to workouts in this plan</p>
</div>

<div data-plan-form-target="photosSection" style="display: none;" class="space-y-3">
<div class="form-field">
<%= form.label :photos, "Upload Training Plan Photos", class: "form-label" %>
Expand Down
14 changes: 14 additions & 0 deletions app/views/plans/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,22 @@
<div class="space-y-1">
<h3 class="text-base font-semibold text-slate-800"><%= plan.length %> Week Plan</h3>
<p class="text-sm text-slate-500">Race Date: <span class="font-medium text-slate-800"><%= plan.race_date.strftime("%b %d, %Y") %></span></p>
<% if plan.respond_to?(:webhook_enabled?) && plan.webhook_enabled? %>
<div class="flex items-center gap-1.5 text-xs text-green-600">
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="font-medium">Strava Sync Enabled</span>
</div>
<% end %>
</div>
<div class="flex gap-2">
<% unless plan.respond_to?(:webhook_enabled?) && plan.webhook_enabled? %>
<%= button_to "Enable Sync", enable_webhook_sync_plan_path(plan),
method: :patch,
class: "btn-soft text-xs",
form: { data: { turbo_confirm: "Enable automatic Strava activity syncing for this plan?" } } %>
<% end %>
<%= link_to "View", plan, class: "btn-outline" %>
<%= link_to "Edit", edit_plan_path(plan), class: "btn-soft" %>
</div>
Expand Down
58 changes: 58 additions & 0 deletions app/views/plans/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@
</div>
</div>

<% if @plan.respond_to?(:webhook_enabled?) && @plan.webhook_enabled? %>
<div class="rounded-lg bg-green-50 border border-green-200 p-4 mt-4">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-green-600 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<div class="flex-1">
<h3 class="text-sm font-semibold text-green-800 mb-1">Strava Activity Sync Enabled</h3>
<p class="text-xs text-green-700">New Strava activities will be automatically synced and matched to workouts in this plan.</p>
</div>
</div>
</div>
<% end %>

<div class="actions-row" data-plan-processor-target="actions">
<%= link_to "Edit plan", edit_plan_path(@plan), class: "btn-soft" %>
<% if @plan.custom? %>
Expand All @@ -52,6 +66,50 @@

<div class="divider"></div>

<!-- Unmatched Activities Section -->
<% if @plan.respond_to?(:webhook_enabled?) && @plan.webhook_enabled? %>
<%
# This will check for unmatched activities when the database supports it
# For now, showing placeholder that will be populated when webhook is implemented
unmatched_activities = @plan.activities.where(matched_workout_id: nil).where.not(strava_id: nil) rescue []
%>
<% if unmatched_activities.any? %>
<div class="rounded-lg bg-amber-50 border border-amber-200 p-4 mb-6">
<div class="flex items-start gap-3 mb-4">
<svg class="w-5 h-5 text-amber-600 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
<div class="flex-1">
<h3 class="text-sm font-semibold text-amber-800 mb-1">Unmatched Strava Activities</h3>
<p class="text-xs text-amber-700 mb-3">The following activities couldn't be automatically matched to workouts in your plan. You can link them manually or dismiss them.</p>

<div class="space-y-2">
<% unmatched_activities.limit(5).each do |activity| %>
<div class="bg-white rounded-lg border border-amber-200 p-3 flex items-center justify-between">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-medium text-slate-800"><%= activity.start_date_local.strftime("%b %d, %Y") %></span>
<span class="badge-accent"><%= activity.distance %> mi</span>
</div>
<p class="text-xs text-slate-600"><%= activity.description.presence || "No description" %></p>
</div>
<div class="flex gap-2 ml-4">
<%= button_to "Link", "#",
method: :post,
class: "btn-soft text-xs px-3 py-1" %>
<%= button_to "Dismiss", "#",
method: :delete,
class: "btn-ghost text-xs px-3 py-1 text-slate-500" %>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
<% end %>

<!-- Processing Spinner -->
<div data-plan-processor-target="processing_indicator" class="<%= @plan.processing_status.in?(%w[queued processing]) ? '' : 'hidden' %>">
<div class="text-center py-12">
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
patch :update_workouts
post :create_blank_schedule
get :processing_status
patch :enable_webhook_sync
end
end
resources :activities
Expand Down
21 changes: 21 additions & 0 deletions test/controllers/plans_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PlansControllerTest < ActionDispatch::IntegrationTest
test "should get new" do
get new_plan_url
assert_response :success
assert_select "input[type=checkbox][name='plan[webhook_enabled]']"
end

test "should create plan" do
Expand Down Expand Up @@ -90,4 +91,24 @@ class PlansControllerTest < ActionDispatch::IntegrationTest

assert_redirected_to plans_url
end

test "should enable webhook sync" do
skip "Requires webhook_enabled column in database" unless @plan.respond_to?(:webhook_enabled)

patch enable_webhook_sync_plan_url(@plan)

@plan.reload
assert @plan.webhook_enabled
assert_redirected_to plans_url
assert_equal "Strava activity sync has been enabled for this plan.", flash[:notice]
end

test "should handle enable webhook sync when column doesn't exist" do
skip "Only applicable when webhook_enabled column doesn't exist" if @plan.respond_to?(:webhook_enabled)

patch enable_webhook_sync_plan_url(@plan)

assert_redirected_to plans_url
assert_match /not available yet/, flash[:alert]
end
end
63 changes: 63 additions & 0 deletions test/system/plans_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,67 @@ def login!
# Should navigate to edit workouts page
assert_current_path edit_workouts_plan_path(Plan.last)
end

test "should show webhook sync checkbox on new plan form" do
visit new_plan_path

# Verify webhook sync checkbox exists
assert_selector "input[type='checkbox'][name='plan[webhook_enabled]']"
assert_selector "label", text: "Enable Strava Activity Sync"
assert_text "Automatically sync new Strava activities"
end

test "should create plan with webhook enabled" do
skip "Requires webhook_enabled column in database" unless Plan.column_names.include?("webhook_enabled")

visit new_plan_path

assert_selector "form"
fill_in "plan[length]", with: 12
fill_in "plan[race_date]", with: (Date.current + 3.months).strftime("%Y-%m-%d")

# Enable webhook sync
check "plan[webhook_enabled]"

click_on "Create Plan"
assert_text "Plan was successfully created"

plan = Plan.last
assert plan.webhook_enabled
end

test "should show enable sync button for plans without webhook" do
skip "Requires webhook_enabled column in database" unless Plan.column_names.include?("webhook_enabled")

# Create a plan without webhook enabled
plan = Plan.create!(
length: 12,
race_date: Date.current + 3.months,
plan_type: "template",
webhook_enabled: false
)

visit plans_path

# Should show Enable Sync button
assert_selector "input[type='submit'][value='Enable Sync']"
end

test "should show webhook status on plan show page" do
skip "Requires webhook_enabled column in database" unless Plan.column_names.include?("webhook_enabled")

# Create a plan with webhook enabled
plan = Plan.create!(
length: 12,
race_date: Date.current + 3.months,
plan_type: "template",
webhook_enabled: true
)

visit plan_path(plan)

# Should show webhook sync status
assert_text "Strava Activity Sync Enabled"
assert_text "New Strava activities will be automatically synced"
end
end