Add new styles for home and landing pages
- Created home.css with comprehensive styles for the home page layout, including typography, buttons, and responsive design adjustments. - Created landing.css to style the landing page, focusing on typography, layout, and responsive behavior for various screen sizes.
This commit is contained in:
@@ -1,3 +1,744 @@
|
||||
export function Home() {
|
||||
return <h1>Home</h1>;
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type FormEvent,
|
||||
} from "react";
|
||||
import { Link } from "react-router";
|
||||
import { DeleteHabit } from "../components/DeleteHabit";
|
||||
import { useAuth } from "../components/AuthProvider";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Counter,
|
||||
} from "../components/design-system/primitives";
|
||||
import { CreateHabit, type HabitStarter } from "../components/CreateHabit";
|
||||
import { HabitHistory } from "../components/HabitHistory";
|
||||
import {
|
||||
formatTrackingDate,
|
||||
habitRequest,
|
||||
scheduleLabel,
|
||||
type TodayHabit,
|
||||
type TodayResponse,
|
||||
} from "../lib/dashboard";
|
||||
import "../../styles/design-system.css";
|
||||
import "../../styles/home.css";
|
||||
|
||||
const starters: (HabitStarter & { description: string; symbol: string })[] = [
|
||||
{
|
||||
name: "Read a little",
|
||||
method: "manual",
|
||||
description: "A few pages. A moment for yourself.",
|
||||
symbol: "↗",
|
||||
color: "#977344",
|
||||
},
|
||||
{
|
||||
name: "Drink water",
|
||||
method: "count",
|
||||
target: 8,
|
||||
unit: "glasses",
|
||||
description: "One glass at a time, throughout the day.",
|
||||
symbol: "+",
|
||||
color: "#426582",
|
||||
},
|
||||
{
|
||||
name: "Evening reset",
|
||||
method: "tasks",
|
||||
tasks: "Clear desk\nPlan tomorrow\nStretch",
|
||||
description: "A small routine to close the day.",
|
||||
symbol: "☾",
|
||||
color: "#79618d",
|
||||
},
|
||||
];
|
||||
|
||||
function CountEntry({
|
||||
habit,
|
||||
disabled,
|
||||
onSave,
|
||||
}: {
|
||||
habit: TodayHabit;
|
||||
disabled: boolean;
|
||||
onSave: (value: number) => void;
|
||||
}) {
|
||||
const [value, setValue] = useState(String(habit.value));
|
||||
useEffect(() => setValue(String(habit.value)), [habit.value]);
|
||||
function submit(event: FormEvent) {
|
||||
event.preventDefault();
|
||||
if (
|
||||
value.trim() &&
|
||||
Number.isInteger(Number(value)) &&
|
||||
Number(value) >= 0 &&
|
||||
Number(value) <= 1_000_000_000
|
||||
)
|
||||
onSave(Number(value));
|
||||
}
|
||||
return (
|
||||
<details className="home-count-entry">
|
||||
<summary>Set a total</summary>
|
||||
<form onSubmit={submit}>
|
||||
<label htmlFor={`count-${habit.habitId}`}>Total {habit.unit}</label>
|
||||
<input
|
||||
id={`count-${habit.habitId}`}
|
||||
type="number"
|
||||
min={0}
|
||||
max={1_000_000_000}
|
||||
step={1}
|
||||
required
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
/>
|
||||
<Button type="submit" variant="secondary" disabled={disabled}>
|
||||
Save total
|
||||
</Button>
|
||||
</form>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
export function Home() {
|
||||
const { user, signOut, busy: authBusy, error: authError, signIn } = useAuth();
|
||||
const [today, setToday] = useState<TodayResponse | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [notice, setNotice] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [filter, setFilter] = useState<"all" | "remaining">("all");
|
||||
const [starter, setStarter] = useState<HabitStarter | null>(null);
|
||||
const [editing, setEditing] = useState<{
|
||||
habit: TodayHabit;
|
||||
color: string;
|
||||
} | null>(null);
|
||||
const [deleting, setDeleting] = useState<TodayHabit | null>(null);
|
||||
const [revision, setRevision] = useState(0);
|
||||
const [failedAvatar, setFailedAvatar] = useState<string | null>(null);
|
||||
const requestId = useRef(0);
|
||||
const mutation = useRef(false);
|
||||
const mounted = useRef(true);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
const id = ++requestId.current;
|
||||
try {
|
||||
const result = await habitRequest<TodayResponse>("/today");
|
||||
if (mounted.current && id === requestId.current) {
|
||||
setToday(result);
|
||||
setError("");
|
||||
setRevision((value) => value + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
if (mounted.current && id === requestId.current)
|
||||
setError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Could not load your habits.",
|
||||
);
|
||||
} finally {
|
||||
if (mounted.current && id === requestId.current) setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
void refresh();
|
||||
const refreshVisible = () => {
|
||||
if (!mutation.current && document.visibilityState !== "hidden")
|
||||
void refresh();
|
||||
};
|
||||
const timer = window.setInterval(refreshVisible, 60_000);
|
||||
window.addEventListener("focus", refreshVisible);
|
||||
document.addEventListener("visibilitychange", refreshVisible);
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
++requestId.current;
|
||||
window.clearInterval(timer);
|
||||
window.removeEventListener("focus", refreshVisible);
|
||||
document.removeEventListener("visibilitychange", refreshVisible);
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
async function log(
|
||||
habit: TodayHabit,
|
||||
value: { count: number } | { done: boolean },
|
||||
taskId?: string,
|
||||
) {
|
||||
if (mutation.current || !today) return;
|
||||
mutation.current = true;
|
||||
++requestId.current;
|
||||
setBusy(true);
|
||||
setError("");
|
||||
setNotice("");
|
||||
try {
|
||||
const updated = await habitRequest<TodayHabit>(
|
||||
`/habits/${habit.habitId}/days/${today.date}/${taskId ? `tasks/${taskId}` : "progress"}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(value),
|
||||
},
|
||||
);
|
||||
if (!mounted.current) return;
|
||||
setToday((current) => {
|
||||
if (!current || current.date !== updated.date) return current;
|
||||
const habits = current.habits.map((item) =>
|
||||
item.habitId === updated.habitId ? updated : item,
|
||||
);
|
||||
return {
|
||||
...current,
|
||||
habits,
|
||||
due: habits.filter((item) => item.due).length,
|
||||
completed: habits.filter((item) => item.complete).length,
|
||||
};
|
||||
});
|
||||
setNotice(`${habit.name} updated. Your progress is saved.`);
|
||||
await refresh();
|
||||
} catch (error) {
|
||||
if (mounted.current)
|
||||
setError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Could not save progress. Please try again.",
|
||||
);
|
||||
} finally {
|
||||
mutation.current = false;
|
||||
if (mounted.current) setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) return null;
|
||||
const empty = today?.habits.length === 0;
|
||||
const due = today?.habits.filter((habit) => habit.due) ?? [];
|
||||
const offDay = today?.habits.filter((habit) => !habit.due) ?? [];
|
||||
const shown =
|
||||
filter === "remaining" ? due.filter((habit) => !habit.complete) : due;
|
||||
const remaining = today ? today.due - today.completed : 0;
|
||||
const name = user.displayName || user.username;
|
||||
|
||||
return (
|
||||
<div className="ds-root home-root" id="top">
|
||||
<a className="ds-skip-link" href="#home-main">
|
||||
Skip to content
|
||||
</a>
|
||||
<header className="ds-header home-header">
|
||||
<Link className="ds-wordmark" to="/" aria-label="Minabot home">
|
||||
minabot.
|
||||
</Link>
|
||||
<nav aria-label="Main navigation">
|
||||
<Link to="/" aria-current="page">
|
||||
Home
|
||||
</Link>
|
||||
<a href="#today">Today</a>
|
||||
<a href={empty ? "#first-habit" : "#rhythm"}>
|
||||
{empty ? "Get started" : "Your rhythm"}
|
||||
</a>
|
||||
</nav>
|
||||
<div className="home-account">
|
||||
{user.avatarUrl && failedAvatar !== user.avatarUrl ? (
|
||||
<img
|
||||
className="home-avatar"
|
||||
src={user.avatarUrl}
|
||||
alt={`${name}’s Discord avatar`}
|
||||
width={30}
|
||||
height={30}
|
||||
referrerPolicy="no-referrer"
|
||||
onError={() => setFailedAvatar(user.avatarUrl)}
|
||||
/>
|
||||
) : (
|
||||
<span className="home-avatar" aria-hidden="true">
|
||||
{name.slice(0, 1).toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
<span className="home-account-name" title={name}>
|
||||
{name}
|
||||
</span>
|
||||
<Button
|
||||
variant="text"
|
||||
disabled={authBusy || busy || !!starter}
|
||||
onClick={() => void signOut()}
|
||||
>
|
||||
{authBusy ? "Signing out…" : "Sign out"}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="ds-main" id="home-main">
|
||||
<section className="home-welcome" aria-labelledby="home-title">
|
||||
<div>
|
||||
<p className="ds-eyebrow">YOUR SPACE / YOUR OWN PACE</p>
|
||||
<h1 id="home-title">
|
||||
{empty ? "Welcome home," : "A little today,"}
|
||||
<br />
|
||||
<em>{name}.</em>
|
||||
</h1>
|
||||
<p className="ds-intro-copy">
|
||||
{empty
|
||||
? "This is your space to build a rhythm. Let’s start with one small thing."
|
||||
: "Pick up where you are. Make room for what matters."}
|
||||
</p>
|
||||
</div>
|
||||
<div className="home-date">
|
||||
<span className="ds-tiny-cross" aria-hidden="true">
|
||||
+
|
||||
</span>
|
||||
{today ? (
|
||||
<>
|
||||
<time dateTime={today.date}>
|
||||
{formatTrackingDate(today.date)}
|
||||
</time>
|
||||
<p>{today.timezone.replaceAll("_", " ")}</p>
|
||||
</>
|
||||
) : (
|
||||
<p>Your daily home base.</p>
|
||||
)}
|
||||
<span className="ds-footnote">A new day. Your own pace.</span>
|
||||
</div>
|
||||
</section>
|
||||
{authError && (
|
||||
<p className="home-error" role="alert">
|
||||
{authError}
|
||||
</p>
|
||||
)}
|
||||
{error && (
|
||||
<div className="home-error" role="alert">
|
||||
<p>
|
||||
{error}
|
||||
{today && " The view below may be out of date."}
|
||||
</p>
|
||||
{error.includes("session has expired") ? (
|
||||
<Button variant="secondary" onClick={signIn}>
|
||||
Sign in again
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={busy}
|
||||
onClick={() => {
|
||||
setLoading(!today);
|
||||
void refresh();
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<p className="home-notice" role="status">
|
||||
{notice}
|
||||
</p>
|
||||
{loading && (
|
||||
<div className="home-state" role="status">
|
||||
Getting your day ready…
|
||||
</div>
|
||||
)}
|
||||
{today && (
|
||||
<>
|
||||
<section className="home-overview" aria-label="Today at a glance">
|
||||
<div>
|
||||
<span className="ds-eyebrow">
|
||||
{empty ? "A FRESH START" : "TODAY’S PROGRESS"}
|
||||
</span>
|
||||
<p className="home-stat">
|
||||
{today.completed}
|
||||
<span> / {today.due}</span>
|
||||
</p>
|
||||
<p>habits complete</p>
|
||||
<progress
|
||||
value={today.completed}
|
||||
max={today.due || 1}
|
||||
aria-label="Habits completed today"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="ds-eyebrow">
|
||||
{empty ? "ONE SMALL STEP" : "STILL TO COME"}
|
||||
</span>
|
||||
<p className="home-stat">
|
||||
{empty ? "01" : String(remaining).padStart(2, "0")}
|
||||
</p>
|
||||
<p>
|
||||
{empty
|
||||
? "is all it takes to begin"
|
||||
: remaining === 1
|
||||
? "habit left for today"
|
||||
: "habits left for today"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="home-overview-note">
|
||||
<span className="ds-eyebrow">
|
||||
{empty
|
||||
? "NO PERFECT START REQUIRED"
|
||||
: today.due === 0
|
||||
? "ROOM TO REST"
|
||||
: remaining === 0
|
||||
? "ENOUGH FOR TODAY"
|
||||
: "A GENTLE REMINDER"}
|
||||
</span>
|
||||
<h2>
|
||||
{empty ? (
|
||||
<>
|
||||
Small is <em>a good start.</em>
|
||||
</>
|
||||
) : today.due === 0 ? (
|
||||
<>
|
||||
A day off <em>counts, too.</em>
|
||||
</>
|
||||
) : remaining === 0 ? (
|
||||
<>
|
||||
You showed up. <em>Enjoy that.</em>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
A little is <em>still something.</em>
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
<p>
|
||||
{empty
|
||||
? "Choose something easy enough to come back to tomorrow."
|
||||
: today.due === 0
|
||||
? "Nothing is scheduled today. Your habits will be here when it’s time."
|
||||
: remaining === 0
|
||||
? "Everything scheduled for today is complete. No extra credit needed."
|
||||
: "Check in as you go. Every bit of progress belongs here."}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
className="ds-section home-today"
|
||||
id="today"
|
||||
aria-labelledby="today-title"
|
||||
>
|
||||
<div className="ds-section-top">
|
||||
<div>
|
||||
<span className="ds-eyebrow">01 / YOUR DAILY CHECK-IN</span>
|
||||
<h2 id="today-title">
|
||||
{empty ? (
|
||||
<>
|
||||
Begin with <em>a little.</em>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Make today <em>your own.</em>
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
disabled={busy || !!error}
|
||||
onClick={() => setStarter({ name: "", method: "manual" })}
|
||||
>
|
||||
{empty ? "Create your first habit" : "New habit"}
|
||||
<span aria-hidden="true">+</span>
|
||||
</Button>
|
||||
</div>
|
||||
{empty ? (
|
||||
<div id="first-habit" className="home-empty">
|
||||
<p>No habits yet. No catching up to do.</p>
|
||||
<p className="ds-muted">
|
||||
Create your own, or use an idea below as a starting point.
|
||||
You can make it yours before saving.
|
||||
</p>
|
||||
<div className="home-starters">
|
||||
{starters.map((item) => (
|
||||
<button
|
||||
type="button"
|
||||
key={item.name}
|
||||
disabled={!!error}
|
||||
onClick={() => setStarter(item)}
|
||||
>
|
||||
<span
|
||||
className="home-starter-symbol"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{item.symbol}
|
||||
</span>
|
||||
<h3>{item.name}</h3>
|
||||
<p>{item.description}</p>
|
||||
<span className="home-starter-action">
|
||||
Make it mine <span aria-hidden="true">↗</span>
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="ds-preview-toolbar">
|
||||
<div
|
||||
className="ds-view-switch"
|
||||
role="group"
|
||||
aria-label="Today’s habits"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={filter === "all"}
|
||||
onClick={() => setFilter("all")}
|
||||
>
|
||||
All today <span>{today.due}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={filter === "remaining"}
|
||||
onClick={() => setFilter("remaining")}
|
||||
>
|
||||
Remaining <span>{remaining}</span>
|
||||
</button>
|
||||
</div>
|
||||
<span className="home-autosave">Saved as you go</span>
|
||||
</div>
|
||||
{shown.length === 0 && (
|
||||
<div className="home-state">
|
||||
<h3>
|
||||
{today.due === 0
|
||||
? "A little breathing room."
|
||||
: "You’re all caught up."}
|
||||
</h3>
|
||||
<p>
|
||||
{today.due === 0
|
||||
? "No habits are scheduled for today. A day off isn’t a missed day."
|
||||
: "All of today’s habits are complete. Take a moment for yourself."}
|
||||
</p>
|
||||
{filter === "remaining" && today.due > 0 && (
|
||||
<Button variant="text" onClick={() => setFilter("all")}>
|
||||
View completed habits →
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="home-habits">
|
||||
{shown.map((habit) => (
|
||||
<article
|
||||
className={`home-habit${habit.complete ? " home-habit--complete" : ""}`}
|
||||
key={habit.habitId}
|
||||
aria-labelledby={`habit-${habit.habitId}`}
|
||||
>
|
||||
<div className="home-habit-heading">
|
||||
<div>
|
||||
<h3 id={`habit-${habit.habitId}`}>{habit.name}</h3>
|
||||
<p className="home-habit-status">
|
||||
{habit.method === "manual"
|
||||
? "SIMPLE CHECK-IN"
|
||||
: habit.method === "count"
|
||||
? "COUNT TARGET"
|
||||
: "TASK ROUTINE"}{" "}
|
||||
· {scheduleLabel(habit.requirements?.schedule)}
|
||||
{habit.complete && " · Complete"}
|
||||
{habit.carriedFrom && (
|
||||
<span>
|
||||
{" "}
|
||||
· Includes progress from {habit.carriedFrom}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="home-check-in">
|
||||
{habit.method === "manual" ? (
|
||||
<Checkbox
|
||||
label="Done"
|
||||
aria-label={`Mark ${habit.name} complete`}
|
||||
checked={habit.complete}
|
||||
disabled={busy || !!error}
|
||||
onChange={(event) =>
|
||||
void log(habit, {
|
||||
done: event.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : habit.method === "count" ? (
|
||||
<Counter
|
||||
label={`${habit.name} (${habit.unit})`}
|
||||
value={habit.value}
|
||||
target={habit.target ?? 1}
|
||||
disabled={busy || !!error}
|
||||
onChange={(count) => void log(habit, { count })}
|
||||
/>
|
||||
) : (
|
||||
<span className="home-task-count">
|
||||
{habit.value}
|
||||
<span> / {habit.target}</span>
|
||||
</span>
|
||||
)}
|
||||
<a
|
||||
className="home-history-link"
|
||||
href={`#history-${habit.habitId}`}
|
||||
aria-label={`View ${habit.name} history`}
|
||||
>
|
||||
↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{habit.method === "tasks" && (
|
||||
<details className="home-task-details">
|
||||
<summary>
|
||||
Tasks for today{" "}
|
||||
<span>
|
||||
{habit.value} / {habit.target}
|
||||
</span>
|
||||
</summary>
|
||||
<div className="home-tasks">
|
||||
{habit.tasks.map((task) => (
|
||||
<Checkbox
|
||||
key={task.taskId}
|
||||
label={task.name}
|
||||
checked={task.done}
|
||||
disabled={busy || !!error}
|
||||
onChange={(event) =>
|
||||
void log(
|
||||
habit,
|
||||
{ done: event.target.checked },
|
||||
task.taskId,
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
)}
|
||||
{habit.method === "count" && (
|
||||
<CountEntry
|
||||
habit={habit}
|
||||
disabled={busy || !!error}
|
||||
onSave={(count) => void log(habit, { count })}
|
||||
/>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
{offDay.length > 0 && (
|
||||
<details className="home-off-day">
|
||||
<summary>
|
||||
Not scheduled today <span>{offDay.length}</span>
|
||||
</summary>
|
||||
<p className="ds-footnote">
|
||||
These habits aren’t included in today’s progress.
|
||||
</p>
|
||||
{offDay.map((habit) => (
|
||||
<div key={habit.habitId}>
|
||||
<span>
|
||||
<strong>{habit.name}</strong>
|
||||
<small>
|
||||
{scheduleLabel(habit.requirements?.schedule)}
|
||||
{habit.method === "tasks" &&
|
||||
" · No tasks due today"}
|
||||
</small>
|
||||
</span>
|
||||
<a href={`#history-${habit.habitId}`}>
|
||||
View history ↗
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
{!empty && (
|
||||
<section
|
||||
className="ds-section home-rhythm"
|
||||
id="rhythm"
|
||||
aria-labelledby="rhythm-title"
|
||||
>
|
||||
<div className="ds-section-top">
|
||||
<div>
|
||||
<span className="ds-eyebrow">02 / THE BIGGER PICTURE</span>
|
||||
<h2 id="rhythm-title">
|
||||
Find <em>your rhythm.</em>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ds-habit-chart-grid">
|
||||
{today.habits.map((habit) => (
|
||||
<HabitHistory
|
||||
key={habit.habitId}
|
||||
habit={habit}
|
||||
date={today.date}
|
||||
revision={revision}
|
||||
disabled={busy || !!error}
|
||||
onEdit={(color) => setEditing({ habit, color })}
|
||||
onDelete={() => setDeleting(habit)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className="ds-footnote">
|
||||
Your real progress, one day at a time. Select a date to look
|
||||
closer. Days off stay out of your score.
|
||||
</p>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<footer className="ds-footer home-footer">
|
||||
<Link className="ds-wordmark" to="/">
|
||||
minabot.
|
||||
</Link>
|
||||
<span>A little, every day.</span>
|
||||
<a href="#top">Back to top ↑</a>
|
||||
</footer>
|
||||
</main>
|
||||
{starter && today && (
|
||||
<CreateHabit
|
||||
date={today.date}
|
||||
starter={starter}
|
||||
onClose={() => setStarter(null)}
|
||||
onCreated={(name) => {
|
||||
setStarter(null);
|
||||
setNotice(`${name} is ready. Your rhythm starts here.`);
|
||||
setFilter("all");
|
||||
void refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{editing?.habit.requirements && today && (
|
||||
<CreateHabit
|
||||
date={today.date}
|
||||
editing={{
|
||||
id: editing.habit.habitId,
|
||||
config: editing.habit.requirements,
|
||||
}}
|
||||
starter={{
|
||||
name: editing.habit.requirements.name,
|
||||
method: editing.habit.requirements.method,
|
||||
color: editing.color,
|
||||
...(editing.habit.requirements.method === "count"
|
||||
? {
|
||||
target: editing.habit.requirements.target,
|
||||
unit: editing.habit.requirements.unit,
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
onClose={() => setEditing(null)}
|
||||
onCreated={(name) => {
|
||||
setEditing(null);
|
||||
setNotice(`${name} updated. Your earlier history is kept.`);
|
||||
void refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{deleting && (
|
||||
<DeleteHabit
|
||||
habit={deleting}
|
||||
onClose={() => setDeleting(null)}
|
||||
onDeleted={() => {
|
||||
setNotice(
|
||||
`${deleting.name} removed from your dashboard. Recorded history is kept.`,
|
||||
);
|
||||
// Reflect a successful deletion even if the following refresh fails.
|
||||
setToday((current) => {
|
||||
if (!current) return current;
|
||||
const habits = current.habits.filter(
|
||||
(habit) => habit.habitId !== deleting.habitId,
|
||||
);
|
||||
return {
|
||||
...current,
|
||||
habits,
|
||||
due: habits.filter((habit) => habit.due).length,
|
||||
completed: habits.filter((habit) => habit.complete).length,
|
||||
};
|
||||
});
|
||||
setDeleting(null);
|
||||
void refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user