[OCaml] High Intensity Training Online
feat return to routine after cancellation
Confirmed cancellation now saves the workout as a partial logbook entry and returns to the Routine tab instead of Home. With no workout in progress, the middle navigation action reverts from 'Current workout' to 'Routine'. The partial entry stays editable through the logbook. Refines the earlier save-and-exit target from Home to the routine.
Changed files
ENHANCEMENTS.org
@@ -62,5 +62,9 @@
62
62
Allow users to submit subjective feedback at any time from the Logbook. Suggest this feedback flow at the end of each workout, while keeping it available independently.
63
63
** TODO Style the cancellation modal to match the UI
64
64
Style the modal cancellation menu so it matches the overall UI aesthetic.
65
Removed:
** TODO Preserve and navigate after modal workout cancellation
65
Added:
** DONE Preserve and navigate after modal workout cancellation
66
66
When cancellation is confirmed, return the Current Workout tab to the Routine tab, save the workout as a partial logbook entry, and keep it editable later.
67
Added:
** TODO Style form dropdowns to match the UI
68
Added:
Style form dropdown menus so they match the overall UI aesthetic.
69
Added:
** TODO Standardize Logbook navigation and add a mobile profile button
70
Added:
Always label the logbook tab `Logbook`. Add a dedicated profile button for the user name in the mobile top navigation bar.
lib/web/handlers.ml
@@ -369,16 +369,17 @@
369
369
| None -> not_found Present.no_workout)
370
370
371
371
(* Leave the current workout unconditionally. Cancelling never shows an
372
Removed:
invalid-form response: the workout in progress is saved as-is — an
373
Removed:
incomplete record the logbook keeps for later editing — and the user is
374
Removed:
sent Home. A forged or missing CSRF token skips the save but still
375
Removed:
redirects Home, so the view is always left cleanly. *)
372
Added:
invalid-form response: the workout in progress is saved as-is — a partial
373
Added:
record the logbook keeps and stays editable later — and the user returns
374
Added:
to the Routine tab, where the middle navigation action reverts from
375
Added:
"Current workout" to "Routine". A forged or missing CSRF token skips the
376
Added:
save but still navigates away, so the view is always left cleanly. *)
376
377
let cancel t trainee request =
377
378
guard_csrf request >>= function
378
Removed:
| Error _ -> redirect_to request Routes.home
379
Added:
| Error _ -> redirect_to request Routes.routine
379
380
| Ok () ->
380
381
Service.finish t.service trainee.Trainee.id ~ended_at:(t.now ())
381
Removed:
>>= fun _ -> redirect_to request Routes.home
382
Added:
>>= fun _ -> redirect_to request Routes.routine
382
383
383
384
let routes t =
384
385
[
lib/web/pages.ml
@@ -849,8 +849,8 @@
849
849
tag "p" []
850
850
[
851
851
txt
852
Removed:
"The workout is saved as-is for later editing, then you \
853
Removed:
return Home.";
852
Added:
"The workout is saved as a partial logbook entry you can \
853
Added:
edit later, then you return to the routine.";
854
854
];
855
855
tag "div"
856
856
[ class_ "button-group" ]
test/test_web.ml
@@ -810,14 +810,14 @@
810
810
post c "/workout" [ ("dream.csrf", token); ("override", "false") ]
811
811
in
812
812
let response = post c "/workout/cancel" [ (* no dream.csrf *) ] in
813
Removed:
(* No invalid-form response: cancelling always redirects home. A
814
Removed:
forged or missing token skips the save, so the workout is left
815
Removed:
untouched and still in progress. *)
813
Added:
(* No invalid-form response: cancelling always navigates away to the
814
Added:
routine. A forged or missing token skips the save, so the workout
815
Added:
is left untouched and still in progress. *)
816
816
Alcotest.(check int)
817
Removed:
"redirects home without an error page" 303 (status response);
817
Added:
"navigates away without an error page" 303 (status response);
818
818
Alcotest.(check bool)
819
Removed:
"redirects to the overview" true
820
Removed:
(List.mem "/" (Dream.headers response "Location"));
819
Added:
"returns to the routine tab" true
820
Added:
(List.mem "/routine" (Dream.headers response "Location"));
821
821
Alcotest.(check int)
822
822
"workout still in progress" 200
823
823
(status (get c "/workout")) );
@@ -846,22 +846,23 @@
846
846
]
847
847
in
848
848
Alcotest.(check int) "recorded a set" 303 (status recorded);
849
Removed:
(* Cancel with a valid token. It saves the log as-is and redirects
850
Removed:
home. *)
849
Added:
(* Cancel with a valid token. It saves the log as a partial entry
850
Added:
and returns to the routine tab. *)
851
851
let token = Option.get (csrf_token (body (get c "/workout"))) in
852
852
let cancelled =
853
853
post c "/workout/cancel" [ ("dream.csrf", token) ]
854
854
in
855
Removed:
Alcotest.(check int) "cancel redirects home" 303 (status cancelled);
855
Added:
Alcotest.(check int)
856
Added:
"cancel returns to the routine" 303 (status cancelled);
856
857
Alcotest.(check bool)
857
Removed:
"redirects to the overview, not the logbook" true
858
Removed:
(List.mem "/" (Dream.headers cancelled "Location"));
858
Added:
"returns to the routine tab, not the logbook" true
859
Added:
(List.mem "/routine" (Dream.headers cancelled "Location"));
859
860
(* No current workout: the workout view redirects away. *)
860
861
Alcotest.(check int)
861
862
"no workout in progress" 303
862
863
(status (get c "/workout"));
863
Removed:
(* The workout was saved as-is: the logbook holds the record and it
864
Removed:
opens for later editing. *)
864
Added:
(* The workout was saved as a partial entry: the logbook holds the
865
Added:
record and it opens for later editing. *)
865
866
let logbook_page = body (get c "/logbook") in
866
867
Alcotest.(check bool)
867
868
"logbook shows the saved workout" true