Een dating-app op basis van brieven, niet van gezichten!

main

Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.

5.9 kB Ruw bestand
import { Fragment, useEffect, useState } from "react";
import { INK, RED, SKY, FONT_MONO } from "../../theme";
import { POST_HOURS } from "../../constants";
import { postState, enablePost, disablePost, setPostHours } from "../../lib/postbode";
import { meldingFout } from "../../lib/plugin-fouten";
import { osNaam } from "../../lib/platform";

/*
 * Notificaties, bij Mijn profiel: een schakelaar en de bezorgmomenten. Uit tot je hem aanzet — een
 * melding is het enige in deze app dat uit zichzelf om aandacht vraagt.
 *
 * Kort gehouden met opzet. Hoe het werkt en waarom het zo werkt staat in de privacyverklaring en de
 * README, niet hier: een instelscherm dat zichzelf verantwoordt is een instelscherm dat je niet
 * leest. De momenten zijn hele uren (Android bepaalt zelf wanneer een app op de achtergrond aan de
 * beurt is, dus minuten zouden precisie beloven die er niet is) en ook te verzetten terwijl de
 * schakelaar uit staat. `getSince()` levert de hoogste brief die de app nu kent; zie lib/postbode.js.
 */
const uurLabel = (u) => String(u).padStart(2, "0") + ":00";
const ALLE_UREN = Array.from({ length: 24 }, (_, u) => u);
/* Wat "uit" is in een keuzemenu: een lege waarde, want een uur is het niet */
const UIT = "";

export default function PostSection({ token, getSince }) {
  const [state, setState] = useState(null); // { enabled, allowed, hours } — null zolang onbekend
  const [busy, setBusy] = useState(false);
  const [denied, setDenied] = useState(false);
  const [fout, setFout] = useState(null); // een afgewezen aanroep, al geduid (zie lib/plugin-fouten)

  /*
   * Elk antwoord van de plugin komt hier langs. Een `fout` is geen state: hij zegt niets over hoe de
   * bezorging ervoor staat, alleen dat deze poging strandde — dus hij gaat apart, en wat er op het
   * scherm stond blijft staan in plaats van te worden overschreven met een halve waarheid.
   */
  const verwerk = (antwoord) => {
    if (!antwoord) return false; // geen plugin (browser, of een app-versie die hem niet kent)
    if (antwoord.fout) {
      setFout(meldingFout(antwoord.fout));
      return false;
    }
    setState((s) => ({ ...s, ...antwoord }));
    return true;
  };

  useEffect(() => {
    let levend = true;
    postState().then((s) => levend && verwerk(s));
    return () => { levend = false; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /*
   * Altijd evenveel keuzemenu's als POST_HOURS momenten kent, gevuld met wat er bewaard is en
   * verder `null` — dat is een menu op "uit". De momenten zijn een verzameling tijdstippen, dus wat
   * terugkomt is oplopend en ontdubbeld; de menu's volgen die volgorde.
   */
  const bewaard = state?.hours ?? POST_HOURS.filter((u) => u !== null);
  const uren = POST_HOURS.map((_, i) => bewaard[i] ?? null);
  const actief = uren.filter((u) => u !== null);

  const schakel = async (aan) => {
    setBusy(true);
    setDenied(false);
    setFout(null);
    const next = aan ? await enablePost({ token, since: getSince(), hours: actief }) : await disablePost();
    if (next?.denied) setDenied(true);
    verwerk(next);
    setBusy(false);
  };

  /* Eén moment verzetten of uitzetten (uur = null). Het laatste moment kan niet uit, zie hieronder. */
  const verzet = async (index, uur) => {
    const volgende = uren.map((u, i) => (i === index ? uur : u)).filter((u) => u !== null);
    setState((s) => ({ ...(s ?? {}), hours: volgende })); // meteen zichtbaar, ook als de plugin traag is
    setFout(null);
    verwerk(await setPostHours(volgende));
  };

  const aan = state?.enabled === true;
  /* Aan in de app, uit in de systeeminstellingen: dan verschijnt er niets en hoort dat er te staan */
  const gesmoord = aan && state?.allowed === false;

  const keuze = {
    padding: "6px 8px", border: `2px solid ${INK}`, background: "#fff", color: INK,
    fontSize: 14, fontWeight: 600, fontFamily: FONT_MONO,
  };

  return (
    <div style={{ marginTop: 36, paddingTop: 16, borderTop: `1px solid ${SKY}` }}>
      <h3 style={{ margin: "0 0 10px", fontSize: 16 }}>Notificaties</h3>
      <label style={{ display: "flex", gap: 8, alignItems: "center", fontSize: 14, marginBottom: 12 }}>
        <input type="checkbox" checked={aan} disabled={busy} onChange={(e) => schakel(e.target.checked)} />
        Zeg het als er post ligt
      </label>
      <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
        <span style={{ fontSize: 14 }}>Postbode komt langs om:</span>
        {uren.map((uur, i) => (
          <Fragment key={i}>
            {i > 0 && <span style={{ fontSize: 14 }}>en</span>}
            <select
              value={uur ?? UIT}
              onChange={(e) => verzet(i, e.target.value === UIT ? null : +e.target.value)}
              style={keuze}
              aria-label={`Bezorgmoment ${i + 1}`}
            >
              {/*
                * "uit" staat er niet bij als dit het énige moment is dat nog aan staat: dan zou de
                * schakelaar aan kunnen staan zonder dat er ooit iets langskomt. Geen bezorging wil
                * je met de schakelaar zeggen, niet met een leeg rijtje tijdstippen.
                */}
              {(uur === null || actief.length > 1) && <option value={UIT}>uit</option>}
              {ALLE_UREN.map((u) => (
                <option key={u} value={u}>{uurLabel(u)}</option>
              ))}
            </select>
          </Fragment>
        ))}
      </div>
      {fout && (
        <p role="alert" style={{ fontSize: 13, color: RED, margin: "10px 0 0" }}>
          Instellen lukte niet: {fout}
        </p>
      )}
      {denied && (
        <p role="alert" style={{ fontSize: 13, color: RED, margin: "10px 0 0" }}>
          {osNaam()} liet de melding niet toe. Zet meldingen aan bij de instellingen van je toestel.
        </p>
      )}
      {gesmoord && (
        <p role="alert" style={{ fontSize: 13, color: RED, margin: "10px 0 0" }}>
          Meldingen staan uit bij de instellingen van je toestel.
        </p>
      )}
    </div>
  );
}