diff --git a/docs/superpowers/plans/2026-08-04-kuendigung-teil-a.md b/docs/superpowers/plans/2026-08-04-kuendigung-teil-a.md index f811cc2..f9071b6 100644 --- a/docs/superpowers/plans/2026-08-04-kuendigung-teil-a.md +++ b/docs/superpowers/plans/2026-08-04-kuendigung-teil-a.md @@ -414,6 +414,131 @@ it('laesst sie nach dem Laufzeitende nicht mehr aendern', function () { --- +## Task 5: Der Streifen im Dashboard, der die Frist zeigt + +**Files:** +- Modify: `app/Livewire/Dashboard.php` +- Modify: `resources/views/livewire/dashboard.blade.php` +- Modify: `lang/{de,en}/dashboard.php` +- Test: `tests/Feature/Cancellation/DashboardCountdownTest.php` + +**Vom Betreiber gefordert, und es ist der richtige Ort:** das Dashboard ist die +Seite, die ein Kunde ohnehin öffnet. Eine Mail sieben Tage vorher kann im +Postfach untergehen; ein Streifen über der eigenen Übersicht nicht. + +Er zeigt, **wie lange der Zugang noch steht** — nicht, wann irgendetwas +gelöscht wird. Das ist die Frist, die den Kunden betrifft: ab dem Laufzeitende +kommt er nicht mehr an seine Cloud. + +Und er ist der natürliche Platz für Aufgabe 4: wer hier liest, dass die Zeit +läuft, will im selben Atemzug seine Antwort zum Export ändern können. + +- [ ] **Schritt 1: Die fehlschlagenden Prüfungen schreiben** + +```php +addDays(12)); + + Livewire::actingAs($user)->test(Dashboard::class) + ->assertSee(__('dashboard.ending.title')) + ->assertSee($instance->service_ends_at->local()->isoFormat('LL')); +}); + +it('zeigt den Streifen nicht, solange nicht gekuendigt wurde', function () { + [$user] = laufendeInstanz(); + + Livewire::actingAs($user)->test(Dashboard::class) + ->assertDontSee(__('dashboard.ending.title')); +}); + +it('zeigt ihn auch nach dem Laufzeitende nicht mehr', function () { + // Danach ist der Zugang zu, und ein Streifen, der eine Restzeit nennt, + // waere schlicht falsch. Was dann gilt, sagt die Seite an anderer Stelle. + [$user] = gekuendigteInstanz(endetIn: now()->subDay(), status: 'ended'); + + Livewire::actingAs($user)->test(Dashboard::class) + ->assertDontSee(__('dashboard.ending.title')); +}); + +it('nennt keine Restzeit, die schon abgelaufen ist', function () { + // Der Fall zwischen den Stuehlen: das Laufzeitende ist vorbei, aber der + // stuendliche Lauf hat die Instanz noch nicht auf `ended` gestellt. Eine + // Zeile "noch -3 Stunden" ist die Sorte Fehler, die niemand meldet und + // jeder sieht. + [$user] = gekuendigteInstanz(endetIn: now()->subHours(3)); + + $html = Livewire::actingAs($user)->test(Dashboard::class)->html(); + + expect($html)->not->toContain('-'); // an der Restzeit, nicht im ganzen Dokument — siehe unten +}); + +it('traegt die Antwort zum Export und laesst sie aendern', function () { + [$user, $instance] = gekuendigteInstanz(endetIn: now()->addDays(12)); + $instance->update(['export_wish' => false]); + + Livewire::actingAs($user)->test(Dashboard::class) + ->assertSee(__('dashboard.ending.export_no')) + ->call('setExportWish', true); + + expect($instance->fresh()->export_wish)->toBeTrue(); +}); +``` + +**Zur vierten Prüfung:** `not->toContain('-')` über das ganze Dokument trägt +nicht — ein Datum enthält Bindestriche, eine CSS-Klasse auch. Schreib sie so, +dass sie **die Restzeit** prüft und nicht die Seite: entweder gegen den Wert, +den die Komponente berechnet, oder gegen den Textbaustein mit eingesetzter +Zahl. Eine Zusicherung, die aus dem falschen Grund grün ist, ist keine. + +- [ ] **Schritt 2: Laufen lassen, Fehlschlag bestätigen** + +- [ ] **Schritt 3: Den Streifen bauen** + +`Dashboard::render()` holt die Instanz bereits (Zeile 36 ff.) — von dort kommt +`service_ends_at`. Der Streifen erscheint, wenn `status === 'cancellation_scheduled'` +**und** das Ende in der Zukunft liegt. + +Die Restzeit geht durch `->local()` (R19) und wird **abgerundet auf ganze Tage +genannt, solange mehr als ein Tag bleibt** — „noch 12 Tage" liest sich, „noch +11 Tage 7 Stunden" nicht. Am letzten Tag zählt er Stunden; das ist der Moment, +in dem die Zahl wirklich zählt. + +Der Streifen benutzt dieselbe Warnfarbe wie der bestehende Zugangsdaten-Kasten +darüber (`border-warning-border bg-warning-bg text-warning`) und steht an +derselben Stelle im Fluss — nicht zwei verschiedene Sprachen für zwei +verschiedene Dringlichkeiten auf einer Seite. + +Er nennt drei Dinge: das Datum, die Restzeit, und **was dann geschieht** — dass +der Zugang zur Cloud endet und ein Selbst-Herunterladen bis dahin möglich ist. +Dazu die Antwort zum Export mit einem Schalter, sie zu ändern (Aufgabe 4). + +- [ ] **Schritt 4: Laufen lassen, grün** + +- [ ] **Schritt 5: Ganze Suite** + +- [ ] **Schritt 6: Festschreiben** + +```bash +git commit -F- <<'MSG' +Ein Streifen im Dashboard sagt, wie lange der Zugang noch steht + +Eine Mail sieben Tage vorher kann im Postfach untergehen; die eigene Uebersicht +oeffnet der Kunde ohnehin. Der Streifen nennt das Datum, die Restzeit und was +danach geschieht — dass der Zugang zur Cloud endet, nicht dass irgendetwas +geloescht wird. Das ist die Frist, die ihn betrifft. + +Und er traegt die Antwort zum Export: wer liest, dass die Zeit laeuft, will es +sich im selben Atemzug anders ueberlegen koennen. +MSG +``` + +--- + ## Selbstdurchsicht **Abdeckung von Teil A des Entwurfs** @@ -422,8 +547,9 @@ it('laesst sie nach dem Laufzeitende nicht mehr aendern', function () { |---|---| | Beim Kündigen wird gefragt | 2 | | Drei Zustände, `null` ≠ „nein" | 1 | -| Antwort später änderbar | 4 | +| Antwort später änderbar | 4, 5 | | Erinnerung vor dem Laufzeitende | 3 | +| Streifen im Dashboard mit der Restfrist | 5 | | Kein Gast angefasst, kein Speicher gebraucht | alle | **Was Teil A ausdrücklich NICHT tut**