habits-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev bc888334fc51
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone e8a688fd21351d04b05468df
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/Start.tsx
// The landing hero's quick start: one tap adds a first habit, no sign-up. Returning people see today.
import { useState } from "react";
import { useStore, listHabits, getState, update, nextOrder } from "./lib/store";
import { stats, newHabit, MAX_NAME } from "./lib/habits";
import { navigate } from "./lib/router";
import { SUGGESTIONS, addSuggestion } from "./Today";
import { A } from "./ui";
import { Plus, Next, Check } from "./icons";

export function Start() {
  const s = useStore();
  const [name, setName] = useState("");
  const habits = listHabits(s);
  if (habits.length) {
    const rows = habits.map((h) => ({ h, st: stats(h, s.weekStart) })).filter((r) => r.h.kind !== "avoid" && (r.st.due || r.st.doneToday));
    const done = rows.filter((r) => r.st.doneToday).length;
    return (
      <div className="start card">
        <p className="eyebrow">Welcome back</p>
        <p className="start-title">{rows.length ? (done === rows.length ? "All done for today." : `${done} of ${rows.length} done today.`) : "Nothing due today."}</p>
        <ul className="start-today">
          {rows.slice(0, 5).map(({ h, st }) => (
            <li key={h.id} className={st.doneToday ? "done" : ""}><span className="start-dot">{st.doneToday && <Check size={14} />}</span>{h.name}</li>
          ))}
        </ul>
        <A className="btn btn-lg btn-block" href="/today">Open today <Next size={16} /></A>
      </div>
    );
  }
  const add = (e: React.FormEvent) => {
    e.preventDefault();
    const n = name.trim();
    if (!n) return;
    const h = newHabit({ name: n.slice(0, MAX_NAME) }, nextOrder(getState()));
    update((x) => { x.habits[h.id] = h; });
    navigate("/today");
  };
  return (
    <div className="start card">
      <p className="start-title">Start a habit now</p>
      <p className="muted small">One tap adds it. No sign-up, no app store.</p>
      <ul className="suggest-list">
        {SUGGESTIONS.map((sg) => (
          <li key={sg.name}>
            <button type="button" className="suggest" onClick={() => { addSuggestion(sg); navigate("/today"); }}>
              <strong>{sg.name}</strong><span>{sg.about}</span><Plus size={18} />
            </button>
          </li>
        ))}
      </ul>
      <form className="start-own" onSubmit={add}>
        <input className="input" value={name} maxLength={MAX_NAME} placeholder="Or name your own" aria-label="Name your own habit" onChange={(e) => setName(e.target.value)} />
        <button type="submit" className="btn">Add</button>
      </form>
    </div>
  );
}

Change history

Point OpenApps links to openapps.free
bc888334fc · 18 hours ago
Bind agent address in policy
1f744a2b83 · 19 hours ago
Use OpenApp service URLs and add contribution strip
a9bb601ee0 · 19 hours ago
Bounded source sync
5646afc3a0 · 1 day ago
Bounded source sync
f1b1bac8d6 · 1 day ago
Bounded source sync
b1e14b41b0 · 1 day ago