Prechádzať zdrojové kódy

134 af: melding bij aan-/afmelden nieuwsbrief (incl. foutafhandeling)

Beide toggle-knoppen op mijnProfiel tonen nu een SnackBar na de call. De tekst
is conditioneel: bij succes "Je bent nu aan-/afgemeld voor de nieuwsbrief", bij
een mislukte call "Er ging iets mis. Probeer het later opnieuw."

Nieuwe custom function nieuwsbriefGelukt(dynamic) herkent succes aan de
geabonneerd-sleutel in de respons; drupalRequest geeft bij een fout [] terug.
Geverifieerd met een verse export + dart analyze (0 errors).

CLAUDE.md: twee nieuwe bevindingen — de "+" in een conditional-tak reageert niet
op Claude's kliks (werkend alternatief: Conditional Value als waarde), en de
output van een Custom Action is wel degelijk bindbaar aan een JSON-argument van
een custom function.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 11 hodín pred
rodič
commit
5254eeac14
2 zmenil súbory, kde vykonal 30 pridanie a 87 odobranie
  1. 30 0
      CLAUDE.md
  2. 0 87
      TASKS.md

+ 30 - 0
CLAUDE.md

@@ -1383,6 +1383,36 @@ er echt een ander widget (bv. een `Text`-label) tussen: dupliceer een bestaande
 `Text` en versleep 'm in de tree, of accepteer de volgorde. Verwijderen van de
 ongewenste kopie gaat gewoon via rechtsklik → "Remove Widget".
 
+**⚠️ De "+"-knop IN een TRUE/FALSE-tak van een Conditional reageert niet op
+Claude's kliks — gebruik in plaats daarvan een conditionele WAARDE.** Bevestigd
+2026-09-22 (taak 134), 8 pogingen: de "+" in de hoofdketen werkt normaal, maar
+diezelfde knop in een tak opent geen menu — niet na een canvas-klik, niet met de
+conditional-node geselecteerd, en de knop krijgt zelfs **geen hover-state** (dus
+de events komen er niet aan). Blijf daar niet op doorproberen.
+**Werkend alternatief, en meestal ook compacter:** laat de conditie niet de
+*actie* splitsen maar de *waarde* bepalen. Eén actie, en als bron van het veld
+**Conditional Value (If/Then/Else)**. Zo werd "twee snackbars in twee takken"
+één snackbar met een if/then/else-tekst — in de export
+`Text(functions.x(...) ? 'A' : 'B')`, precies hetzelfde gedrag met drie nodes
+minder. Recept: het ⚏ naast het label **Value** → zoek `Conditional` → **klik de
+bronregel "Conditional Value (If/Then/Else)" zélf** (de rode *"No Available
+Options"* eronder is misleidend, net als bij Combine Text) → dan verschijnt de
+IF/THEN/ELSE-builder met een potlood bij IF en een tekstveld per tak.
+
+**✅ De output van een Custom Action (`Future<dynamic>`) ÍS wél te binden aan een
+JSON-argument van een custom function.** Nuanceert de waarschuwing elders in dit
+bestand — die gaat over binden aan een **getypt veld** (App State, page state),
+en dáár blijft het waar. Bevestigd 2026-09-22 op `drupalRequest`: zet op de
+actie een **Action Output Variable Name**, en dan staat die output gewoon onder
+*Action Outputs* in de Set-Variable-dialoog van een custom-function-argument van
+type **Json**. Levert `functions.x(_model.mijnOutput)` op. Twee dingen die
+daarbij horen: de zoekterm `<naam>` laat maar één bron over, dus de optielijst
+rendert leeg → **hover-truc plus blinde klik ±35 px onder de bronregel, in
+dezelfde `browser_batch`**; en de functie moet de rommel zelf opvangen
+(`if (x is! Map) return false;`), want bij een fout geeft `drupalRequest` `[]`.
+Dit is precies de manier om "is de call gelukt?" in de UI te gebruiken zonder
+dat `drupalRequest` zijn contract hoeft te veranderen.
+
 **Een bestaande actie in een conditionele tak krijgen: Copy Action Chain →
 Delete Action → Add Conditional → Paste in de tak — en tel daarna de
 parameters.** Werkend recept, 2026-09-20 drie keer gebruikt (ongepubliceerde

+ 0 - 87
TASKS.md

@@ -28,7 +28,6 @@ Google Play en één doorloop die alleen Bob kan doen.
 |---|---|---|
 | **FOTOBOEK** | Fotoboek: foto's kiezen uit Drupal + beheren | **Claude — bezig** (2026-09-22/23). **Drupal AF.** **App: bottom sheet werkt op de eerste plek** (logo op `uitgaansevenementAanmaken`) — `FotoboekGridComponent` = `Column > TextField(zoek) + Expanded(GridView)`, component state `zoekterm` → Backend Query `zoek`, en een tik roept `bijKeuze(fid, url)` aan en sluit de sheet. `dart analyze` 0 errors. **⚠️ EERST DOEN (Bob, 1 veld): de sheet heeft nog GEEN hoogte** — `ButtonFotoboekLogo` → Actions → de *Bottom Sheet*-actie → **Height (Optional)** op **85 %** (klik eerst het `%`-teken rechts van het label). Zonder hoogte comprimeert de sheet het component tot zijn natuurlijke grootte, en een `Expanded` in een onbegrensde Column geeft dan een leeg vlak. Claude kwam hier niet bij: het veld ligt ~5 px van de vensterrand, elke klik landde op *Select Component* of op de actie-dropdown (4 pogingen). **Daarna testen** met een profile-build op `uitgaansevenementAanmaken`. **Rest:** (a) samenvoegen tot ÉÉN knop *Logo kiezen* → `Alert Dialog → Confirm Dialog` met knoppen Fotoboek/Mobiel → conditional: TRUE = de sheet, FALSE = de bestaande upload-keten (via Copy Action Chain → Delete → Add Conditional → Paste); daarna mag *Uit fotoboek* weg, plus de nu ongebruikte inline `Container` + page state `fotoboekOpen`/`fotoboekDoel`; (b) uitrollen naar de 3 andere upload-plekken (foto's hier, logo + foto's op `stadsactiviteitAanmaken`); (c) 5e tab "Mijn foto's" op `mijnProfiel` voor beheer/verwijderen; (d) EN-vertaling van het zoekveld (Hint Text). **Opruimen (Bob):** `parameter1..4` op `FotoboekGridComponent` (door *Convert to Component* gemaakt, nergens gelezen) en het wegwerp-component `FotoboekGridComponentCopy`. Endpoint-spec + builder-recepten staan in `CLAUDE.md` |
 | **133** | Kopie-knop verkleinen (mijnProfiel 2x + MijnAanmeldingen) | **Claude — bezig** (plan af 2026-09-22, wacht op groen licht voor de browser). Variant A (icoon in de rij) op `mijnProfiel`, variant B (compacte chip) op `MijnAanmeldingen`, tooltip erbij, kleuren meteen goed. Volledig plan + terugvalopties onderaan dit bestand |
-| **134** | Melding bij aan-/afmelden nieuwsbrief | **Claude — bezig** (sessie 2026-09-22, Bob gaf groen licht voor de builder) (2026-09-22, Bob's wens). Op `mijnProfiel` tab "Nieuwsbrieven" veranderen de twee toggle-knoppen wel zichtbaar, maar er komt geen bevestiging. Toevoegen: op **beide** `FlutterFlowIconButton`s een tweede actie **Show Snack Bar** ná de bestaande `drupalRequest`. Teksten: `toggle_off` (aanmelden) → "Je bent nu aangemeld voor de nieuwsbrief", `toggle_on` (afmelden) → "Je bent nu afgemeld voor de nieuwsbrief". Snackbar-teksten krijgen géén i18n-sleutel (letterlijke string, net als de bestaande "Dit item staat nog in beoordeling"-snackbar op dezelfde pagina), dus geen EN-vertaling nodig. **Optioneel vervolg:** `drupalRequest` geeft bij een fout stil `[]` terug, dus de melding liegt bij een mislukte call — zie 134-B onderaan dit bestand |
 | **G0.6** | Play-screenshots | ✅ Nieuwe set van 10 klaar (2026-09-19 avond). Alleen `tel-04-horeca.png` moet over zodra *Show Test Ads* uit staat |
 
 | **112** | Restje zoek-stad: lege API-call opruimen | **Eigenaar: Bob.** `SelectStateDropDownComponent` → Actions → On Page Load → *Conditional Action 5* (`gemeenteSelectId is set/non-empty`) → TRUE-tak → **Action 13, Custom Action `drupalRequest`** met url/sessionName/sessionId/token/body allemaal Unset. Staat sinds 2026-09-20 op **disabled**; kan weg via ⋮ → Delete Action. Vlak eronder staat **Conditional Action 16** (`apiGemeenteResultInitalize -> Succeeded`) met twee lege takken — mag in dezelfde moeite weg |
@@ -2260,89 +2259,3 @@ tab sluiten). Zie het draaiboek.
 
 **Volgorde:** A t/m C zijn besluiten en blokkeren D. F blokkeert G. K moet af
 vóór de Play-inzending als 130 in de eerste release meegaat.
-
----
-
-## 🔔 134 · Melding bij aan-/afmelden nieuwsbrief · Eigenaar: **Claude — bezig**
-
-**Wat er nu gebeurt** (`mijn_profiel_widget.dart` regels ~2285-2355): beide
-toggle-knoppen in de nieuwsbrievenlijst doen precies één actie —
-`drupalRequest('POST', '.../nieuwsbrieven/subscribe.json' | '.../unsubscribe.json', …)`
-— gevolgd door FlutterFlow's automatische `safeSetState(() {})`. Die rebuild
-bouwt de `FutureBuilder` opnieuw op met een verse `NieuwsbrievenCall.call(...)`,
-dus de toggle springt om zodra de server de nieuwe stand teruggeeft. Er is geen
-enkele bevestiging aan de gebruiker.
-
-### 134-A · Snackbar op beide knoppen (het eigenlijke werk)
-
-Per knop, in de widget tree van `mijnProfiel` (filter op `IconButton`; de twee
-staan direct onder elkaar in de nieuwsbrief-`Row`):
-
-1. Knop selecteren → **Actions**-tab → de bestaande keten openen (*Edit* naast
-   "Action Flow Editor" werkt het prettigst; klap de trigger-kolom in met het
-   **`|<`**-icoontje linksboven).
-2. **"+"** onder de `drupalRequest`-node → *Add Action* → zoekveld →
-   `Snack Bar` → **Show Snack Bar** (categorie *Alerts/Notifications*).
-3. **Snack Bar Message** invullen:
-   - knop met icoon **`toggle_off`** (= de aanmeldknop, staat achter
-     `if (!nieuwsbriefAan(item))`) → `Je bent nu aangemeld voor de nieuwsbrief`
-   - knop met icoon **`toggle_on`** (= de afmeldknop) →
-     `Je bent nu afgemeld voor de nieuwsbrief`
-
-**Twee dingen die tijd schelen:**
-- Het actie-zoekveld wil **klikken en typen in aparte tool-aanroepen**, en het
-  **Snack Bar Message**-veld lijdt aan het bekende "ziet er opgeslagen uit, was
-  het niet"-patroon (precedent 2026-08-17 op de Inloggen-knop: eerste keer typen
-  gaf `content: Text('')` in de export). Verifieer dus met een verse export dat
-  de tekst er echt staat, niet met het paneel.
-- Een snackbar-tekst is een **letterlijke string, geen `getText()`** — hij krijgt
-  dus géén vertaalsleutel en er is geen EN-vertaling nodig. Dat is consistent met
-  de bestaande snackbar op dezelfde pagina ("Dit item staat nog in beoordeling en
-  is nog niet online.").
-
-**Verificatie:**
-`grep -n "Je bent nu aan\|Je bent nu af" lib/mijn_profiel/mijn_profiel_widget.dart`
-hoort twee treffers te geven, elk binnen een `ScaffoldMessenger…showSnackBar`.
-
-### 134-B · Optioneel vervolg: niet liegen bij een mislukte call
-
-`drupalRequest` geeft bij élke fout (non-2xx, timeout, exception) stil `[]`
-terug. De snackbar uit 134-A meldt dan toch "je bent nu aangemeld", terwijl de
-toggle blijft staan waar hij stond — een tegenstrijdigheid op het scherm.
-
-Bij succes geeft de server juist de **verse rij** terug
-(`_custom_nieuwsbrieven_wijzig()` eindigt op `_custom_nieuwsbrieven_rij(...)`,
-dus een Map met `tid`, `naam`, `omschrijving`, `status`, `geabonneerd`).
-Succes is dus te herkennen aan "is een Map met een `geabonneerd`-sleutel".
-
-Stappen:
-1. Nieuwe **custom function** `nieuwsbriefGelukt(dynamic item)`, return **Boolean
-   met Nullable uit**, body plat (geen geneste closure):
-   ```dart
-   if (item is! Map) return false;
-   return item.containsKey('geabonneerd');
-   ```
-2. Op de `drupalRequest`-actie een **Action Output Variable Name** zetten
-   (bv. `nieuwsbriefResultaat`) — die staat er nu niet.
-3. Ná de call een **Add Conditional** met als conditie die custom function over
-   de action output: TRUE-tak → de snackbar uit 134-A, FALSE-tak → snackbar
-   `Er ging iets mis. Probeer het later opnieuw.`
-
-⚠️ **Onzeker punt:** de output van een Custom Action is `Future<dynamic>`, en
-`CLAUDE.md` documenteert dat zo'n output **niet** aan een getypt veld te binden
-is (de optielijst rendert leeg). Voor een custom-function-argument van type
-**JSON** zou het wel moeten kunnen — dat is hetzelfde patroon als
-`isOngepubliceerd(dynamic item)` — maar dat is met een *loop-item* als bron
-bewezen, niet met een action output. Loopt de binding vast: **Remove** klikken
-(een halve binding blokkeert de export project-breed) en 134-B laten vallen;
-134-A staat er dan gewoon.
-
-### Niet nodig
-
-De aanmelding is **direct actief**, geen bevestigingsmail: `custom.nieuwsbrieven.inc`
-leest `variable_get('custom_nieuwsbrieven_confirm', FALSE)` en die variabele is op
-productie niet gezet (nagemeten 2026-09-22 met `drush vget` → "No matching
-variable found"). De tekst "Je bent nu aangemeld" klopt dus; er hoeft geen
-"controleer je e-mail"-variant bij. Zet iemand die variabele ooit op TRUE, dan
-moet de aanmeld-melding mee veranderen (status wordt dan `2` = wacht op
-bevestiging).