fix: restore sync spinner, fix task hover menu, and add custom motivational quote sources

- Reverted RefreshCcw icon to original CSS loading spinner

- Fixed GridTaskBlock overflow to restore task actions menu visibility

- Implemented motivationalQuote state and fetcher with custom API source setting

- Removed redundant Goal of the Week setting from panel
This commit is contained in:
mARTin 2026-02-23 21:53:17 +01:00
parent f9d578ddf1
commit 6fed420375
2 changed files with 90 additions and 52 deletions

View File

@ -180,7 +180,7 @@ export function GridTaskBlock({
boxShadow: (isNotesOpen || isSubTasksOpen || isResizing) ? "0 1px 3px rgba(0,0,0,0.05)" : "none",
display: "flex",
flexDirection: "column",
overflow: isNotesOpen || isSubTasksOpen ? "visible" : "hidden",
overflow: "visible",
}}
draggable={!editingTaskId && !isResizing}
onDragStart={(e) => handleDragStart(e, task)}

View File

@ -539,6 +539,7 @@ export default function WeeklyView() {
todayHighlightColor?: string;
pastDayColor?: string;
goalFallbackType?: "quote" | "next_todo" | "default";
quoteSourceUrl?: string;
goalDefaultSentence?: string;
goalFontFamily?: string;
goalFontSize?: string;
@ -607,7 +608,9 @@ export default function WeeklyView() {
weekendColorSat: "#666666",
weekendColorSun: "#dc2626",
pastDayColor: "#a6a6a7",
quoteSourceUrl: "https://recite.vercel.app/api/random",
});
const [motivationalQuote, setMotivationalQuote] = useState("");
const [showSummary, setShowSummary] = useState(false);
const [isAddingSomedayList, setIsAddingSomedayList] = useState(false);
@ -940,14 +943,43 @@ export default function WeeklyView() {
}
};
const fetchMotivationalQuote = useCallback(async () => {
if (profile.goalFallbackType !== "quote") return;
try {
const url = profile.quoteSourceUrl || "https://recite.vercel.app/api/random";
const res = await fetch(url);
if (!res.ok) throw new Error("Failed to fetch quote");
const data = await res.json();
// Handle different JSON formats (array or object)
let quoteText = "";
if (Array.isArray(data) && data.length > 0) {
const item = data[0];
quoteText = item.quote || item.text || item.content || (typeof item === 'string' ? item : "");
if (item.author) quoteText += ` - ${item.author}`;
} else if (data && typeof data === 'object') {
quoteText = data.quote || data.text || data.content || "";
if (data.author) quoteText += ` - ${data.author}`;
} else if (typeof data === 'string') {
quoteText = data;
}
if (quoteText) setMotivationalQuote(quoteText);
} catch (error) {
console.error("Error fetching motivational quote:", error);
setMotivationalQuote("Stay focused and productive.");
}
}, [profile.goalFallbackType, profile.quoteSourceUrl]);
// Fetch tasks on mount
useEffect(() => {
if (session) {
fetchTasks();
fetchConnections();
fetchCalendarEvents();
fetchMotivationalQuote();
}
}, [session]);
}, [session, fetchMotivationalQuote]);
// Periodic pull-sync from Google Tasks (every 2 minutes)
useEffect(() => {
@ -1104,18 +1136,21 @@ export default function WeeklyView() {
setGoal(newGoal);
try {
await fetch("/api/goal", {
method: "PUT",
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
weekStart: goalDateKey,
text: newGoal,
goal: newGoal,
scope: profile.goalScope || "week",
}),
});
} catch (err) {
console.error("Failed to save goal:", err);
} catch (error) {
console.error("Error saving goal:", error);
}
};
const handleSomedayWheel = (e: React.WheelEvent) => {
if (e.currentTarget) {
e.currentTarget.scrollLeft += e.deltaY;
@ -3277,10 +3312,10 @@ export default function WeeklyView() {
{/* Week & Year */}
<div className="whitespace-nowrap flex items-center gap-2">
{(isLoading || isSyncing || syncStatus === "syncing") && (
<RefreshCcw
className="animate-spin text-blue-600 mr-2"
size={18}
/>
<div
className="animate-spin rounded-full h-4 w-4 border-2 border-gray-300 border-t-blue-600 mr-2"
title="Syncing..."
></div>
)}
<span style={{
fontFamily: profile.cwFontFamily || "Inter",
@ -3364,9 +3399,10 @@ export default function WeeklyView() {
return a.order - b.order;
});
const nextTask = todaysTasks[0];
return nextTask ? `Do this now: ${nextTask.title}` : goal;
const nextTaskText = nextTask ? `Do this now: ${nextTask.title}` : (goal || (profile.goalFallbackType === "quote" ? motivationalQuote : goal));
return nextTaskText;
})()
: goal}
: (goal || (profile.goalFallbackType === "quote" ? motivationalQuote : goal))}
</span>
)}
<span className="text-gray-300 mx-2">-</span>
@ -4145,7 +4181,7 @@ export default function WeeklyView() {
onEdit={() => setEditingTaskId(task.id)}
onUpdate={(newTitle) => updateTask(task.id, newTitle)}
onDelete={() => deleteTask(task.id)}
onNotes={(notes) => updateTaskNotes(task.id, notes)}
onNotes={() => setSelectedTaskForNotes(task)}
onRollToggle={() => toggleTaskRolling(task.id)}
onRecurrence={() =>
setSelectedTaskForRecurrence(task)
@ -6274,6 +6310,7 @@ function SettingsSidebar({
todayHighlightColor?: string;
pastDayColor?: string;
goalFallbackType?: "quote" | "next_todo" | "default";
quoteSourceUrl?: string;
goalDefaultSentence?: string;
goalFontFamily?: string;
goalFontSize?: string;
@ -6316,6 +6353,8 @@ function SettingsSidebar({
hourLabelFormat: "short",
showSubHourSlots: true,
allDayPosition: "below",
goalFallbackType: "quote",
quoteSourceUrl: "https://recite.vercel.app/api/random",
headlineFont: "Inter",
headlineFontSize: "1.25rem",
headlineFontWeight: "900",
@ -8833,45 +8872,7 @@ function SettingsSidebar({
<div
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
>
{/* Goal of the Week */}
<div
style={{
background: "var(--weekly-settings-item-bg)",
padding: "20px",
borderRadius: "12px",
border: "1px solid var(--weekly-border)",
}}
>
<label
style={{
display: "block",
fontSize: "0.9rem",
fontWeight: 600,
marginBottom: "8px",
color: "var(--weekly-settings-title)",
}}
>
{t.goalOfWeek}
</label>
<input
type="text"
value={goal}
onChange={(e) => setGoal(e.target.value)}
onBlur={() => saveGoal(goal)}
onKeyDown={(e) => e.key === "Enter" && e.currentTarget.blur()}
className="weekly-input"
placeholder={t.goalOfWeek}
style={{
width: "100%",
padding: "12px",
fontSize: "1rem",
borderRadius: "6px",
border: "1px solid var(--weekly-border)",
background: "var(--weekly-bg)",
color: "var(--weekly-settings-text)",
}}
/>
</div>
{/* Replaced Goal of the Week settings block */}
{/* "Do This Now" Toggle */}
<div
style={{
@ -9110,6 +9111,43 @@ function SettingsSidebar({
<option value="default">Standardtext</option>
</select>
</div>
{profile.goalFallbackType === "quote" && (
<div style={{ marginTop: "12px" }}>
<label
style={{
display: "block",
fontSize: "0.9rem",
marginBottom: "4px",
color: "var(--weekly-settings-label)",
}}
>
API-Datenquelle (URL)
</label>
<input
type="text"
value={profile.quoteSourceUrl || ""}
onChange={(e) =>
setProfile((p) => ({
...p,
quoteSourceUrl: e.target.value,
}))
}
className="weekly-input"
placeholder="https://recite.vercel.app/api/random"
style={{
width: "100%",
padding: "10px",
fontSize: "0.95rem",
borderRadius: "6px",
border: "1px solid var(--weekly-border)",
background: "var(--weekly-bg)",
}}
/>
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}>
URL that returns a JSON list or object of quotes (e.g. {'[{"quote":"...","author":"..."}]'} or {'{"quote":"..."}'}).
</p>
</div>
)}
{profile.goalFallbackType === "default" && (
<div style={{ marginTop: "12px" }}>
<label