From bb01f0213fdbb5f0cc3354fe5bc6061ca4615f19 Mon Sep 17 00:00:00 2001 From: chelsea Date: Sun, 15 Feb 2026 18:03:19 -0600 Subject: [PATCH] added routine timeline thingy --- .../dashboard/routines/[id]/launch/page.tsx | 19 +- .../src/app/dashboard/routines/[id]/page.tsx | 397 ++++++++++++------ 2 files changed, 292 insertions(+), 124 deletions(-) diff --git a/synculous-client/src/app/dashboard/routines/[id]/launch/page.tsx b/synculous-client/src/app/dashboard/routines/[id]/launch/page.tsx index 651ad4c..118b2b3 100644 --- a/synculous-client/src/app/dashboard/routines/[id]/launch/page.tsx +++ b/synculous-client/src/app/dashboard/routines/[id]/launch/page.tsx @@ -131,7 +131,7 @@ export default function LaunchScreen() { {/* Implementation Intention */} - {(routine.habit_stack_after || (schedule && routine.location)) && ( + {(routine.habit_stack_after || schedule) && (
{routine.habit_stack_after && (

@@ -139,10 +139,21 @@ export default function LaunchScreen() { {routine.name}

)} - {schedule && routine.location && ( + {schedule && (

- at {schedule.time} in{' '} - {routine.location} + {schedule.days.length > 0 && ( + + {schedule.days.length === 7 ? 'Every day' : + schedule.days.length === 5 && ['mon','tue','wed','thu','fri'].every(d => schedule.days.includes(d)) ? 'Weekdays' : + schedule.days.map(d => d.charAt(0).toUpperCase() + d.slice(1)).join(', ')} + + )} + {schedule.time && ( + <>{schedule.days.length > 0 ? ' at ' : 'At '}{schedule.time} + )} + {routine.location && ( + <> in {routine.location} + )}

)}
diff --git a/synculous-client/src/app/dashboard/routines/[id]/page.tsx b/synculous-client/src/app/dashboard/routines/[id]/page.tsx index e054f5e..136f727 100644 --- a/synculous-client/src/app/dashboard/routines/[id]/page.tsx +++ b/synculous-client/src/app/dashboard/routines/[id]/page.tsx @@ -25,8 +25,31 @@ interface Routine { habit_stack_after?: string; } +interface Schedule { + days: string[]; + time: string; + remind: boolean; +} + const ICONS = ['✨', '🌅', '🌙', '☀️', '💪', '🧘', '📚', '🍳', '🏃', '💼', '🎯', '⭐', '🔥', '💤', '🧠']; +const DAY_OPTIONS = [ + { value: 'mon', label: 'Mon' }, + { value: 'tue', label: 'Tue' }, + { value: 'wed', label: 'Wed' }, + { value: 'thu', label: 'Thu' }, + { value: 'fri', label: 'Fri' }, + { value: 'sat', label: 'Sat' }, + { value: 'sun', label: 'Sun' }, +]; + +function formatDays(days: string[]): string { + if (days.length === 7) return 'Every day'; + if (days.length === 5 && ['mon', 'tue', 'wed', 'thu', 'fri'].every(d => days.includes(d))) return 'Weekdays'; + if (days.length === 2 && ['sat', 'sun'].every(d => days.includes(d))) return 'Weekends'; + return days.map(d => DAY_OPTIONS.find(o => o.value === d)?.label || d).join(', '); +} + export default function RoutineDetailPage() { const router = useRouter(); const params = useParams(); @@ -46,10 +69,19 @@ export default function RoutineDetailPage() { const [newStepName, setNewStepName] = useState(''); const [newStepDuration, setNewStepDuration] = useState(5); + // Schedule state + const [schedule, setSchedule] = useState(null); + const [editDays, setEditDays] = useState([]); + const [editTime, setEditTime] = useState('08:00'); + const [editRemind, setEditRemind] = useState(true); + useEffect(() => { const fetchRoutine = async () => { try { - const data = await api.routines.get(routineId); + const [data, scheduleData] = await Promise.all([ + api.routines.get(routineId), + api.routines.getSchedule(routineId).catch(() => null), + ]); setRoutine(data.routine); setSteps(data.steps); setEditName(data.routine.name); @@ -58,6 +90,12 @@ export default function RoutineDetailPage() { setEditLocation((data.routine as Routine).location || ''); setEditHabitStack((data.routine as Routine).habit_stack_after || ''); setEditEnvPrompts((data.routine as Routine).environment_prompts || []); + if (scheduleData) { + setSchedule(scheduleData); + setEditDays(scheduleData.days || []); + setEditTime(scheduleData.time || '08:00'); + setEditRemind(scheduleData.remind ?? true); + } } catch (err) { console.error('Failed to fetch routine:', err); router.push('/dashboard/routines'); @@ -82,6 +120,20 @@ export default function RoutineDetailPage() { habit_stack_after: editHabitStack || null, environment_prompts: editEnvPrompts, }); + + // Save or delete schedule + if (editDays.length > 0) { + await api.routines.setSchedule(routineId, { + days: editDays, + time: editTime, + remind: editRemind, + }); + setSchedule({ days: editDays, time: editTime, remind: editRemind }); + } else if (schedule) { + await api.routines.deleteSchedule(routineId); + setSchedule(null); + } + setRoutine({ ...routine!, name: editName, @@ -120,6 +172,12 @@ export default function RoutineDetailPage() { } }; + const toggleDay = (day: string) => { + setEditDays(prev => + prev.includes(day) ? prev.filter(d => d !== day) : [...prev, day] + ); + }; + const totalDuration = steps.reduce((acc, s) => acc + (s.duration_minutes || 0), 0); if (isLoading) { @@ -157,110 +215,191 @@ export default function RoutineDetailPage() {
{isEditing ? ( -
-
- -
- {ICONS.map((i) => ( - - ))} -
-
-
- - setEditName(e.target.value)} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" - /> -
-
- - setEditDescription(e.target.value)} - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" - /> -
-
- - setEditLocation(e.target.value)} - placeholder="Where do you do this? e.g., bathroom, kitchen" - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" - /> -
-
- - setEditHabitStack(e.target.value)} - placeholder="What do you do right before? e.g., finish breakfast" - className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" - /> -
-
- -

Quick checklist shown before starting

-
- {editEnvPrompts.map((prompt, i) => ( -
- {prompt} + <> +
+
+ +
+ {ICONS.map((i) => ( -
- ))} + ))} +
-
+
+ setNewEnvPrompt(e.target.value)} - placeholder="e.g., Water bottle nearby?" - className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500 outline-none" - onKeyDown={(e) => { - if (e.key === 'Enter' && newEnvPrompt.trim()) { - setEditEnvPrompts([...editEnvPrompts, newEnvPrompt.trim()]); - setNewEnvPrompt(''); - } - }} + value={editName} + onChange={(e) => setEditName(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" /> - +
+
+ + setEditDescription(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ + setEditLocation(e.target.value)} + placeholder="Where do you do this? e.g., bathroom, kitchen" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ + setEditHabitStack(e.target.value)} + placeholder="What do you do right before? e.g., finish breakfast" + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+ +

Quick checklist shown before starting

+
+ {editEnvPrompts.map((prompt, i) => ( +
+ {prompt} + +
+ ))} +
+
+ setNewEnvPrompt(e.target.value)} + placeholder="e.g., Water bottle nearby?" + className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-indigo-500 outline-none" + onKeyDown={(e) => { + if (e.key === 'Enter' && newEnvPrompt.trim()) { + setEditEnvPrompts([...editEnvPrompts, newEnvPrompt.trim()]); + setNewEnvPrompt(''); + } + }} + /> + +
+ + {/* Schedule Editor */} +
+

Schedule

+
+ +
+ {DAY_OPTIONS.map((day) => ( + + ))} +
+
+ {editDays.length > 0 && ( + <> +
+ + setEditTime(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 outline-none" + /> +
+
+
+

Send reminder

+

Get notified when it's time

+
+ +
+ + )} + {schedule && ( + + )} +
+ + {/* Save/Cancel */}
-
+ ) : ( -
-
-
- {routine.icon || '✨'} -
-
-

{routine.name}

- {routine.description && ( -

{routine.description}

- )} -
- - - {totalDuration} min - - {steps.length} steps + <> +
+
+
+ {routine.icon || '✨'} +
+
+

{routine.name}

+ {routine.description && ( +

{routine.description}

+ )} +
+ + + {totalDuration} min + + {steps.length} steps +
+
- -
+ + {/* Schedule display (view mode) */} + {schedule && schedule.days.length > 0 && ( +
+
+ +

Schedule

+
+

+ {formatDays(schedule.days)} at {schedule.time} +

+ {schedule.remind && ( +

Reminders on

+ )} +
+ )} + )} {/* Steps */} @@ -316,7 +473,7 @@ export default function RoutineDetailPage() { Tip: Routines with 4-7 steps tend to feel more manageable. Consider combining related steps.

)} - +