From 3cdcfb7266ed09b47424ca492197b186a619f557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Mon, 2 Mar 2026 13:39:59 +0100 Subject: [PATCH] migrate mobile components to konsta ui, fix german strings Co-Authored-By: Claude Opus 4.6 --- src/features/feed/components/feed-empty.tsx | 10 +- src/features/feed/components/feed-item.tsx | 22 +- src/features/feed/components/feed-list.tsx | 17 +- .../components/politician-search.tsx | 102 ++-- .../components/notification-guide.tsx | 167 +++--- .../settings/components/settings-page.tsx | 516 ++++++++---------- src/features/topics/components/topic-list.tsx | 65 +-- 7 files changed, 397 insertions(+), 502 deletions(-) diff --git a/src/features/feed/components/feed-empty.tsx b/src/features/feed/components/feed-empty.tsx index 1f6fd0d..e557d8f 100644 --- a/src/features/feed/components/feed-empty.tsx +++ b/src/features/feed/components/feed-empty.tsx @@ -1,8 +1,12 @@ +import { Block } from "konsta/react" + export function FeedEmpty() { return ( -
+

Dein Feed ist leer

-

Folge Themen oder Abgeordneten, um Abstimmungen hier zu sehen.

-
+

+ Folge Themen oder Abgeordneten, um Abstimmungen hier zu sehen. +

+ ) } diff --git a/src/features/feed/components/feed-item.tsx b/src/features/feed/components/feed-item.tsx index e432b4e..3cd647a 100644 --- a/src/features/feed/components/feed-item.tsx +++ b/src/features/feed/components/feed-item.tsx @@ -1,4 +1,4 @@ -import { Badge } from "@/shared/components/ui/badge" +import { Chip } from "konsta/react" import type { FeedItem as FeedItemType } from "../lib/assemble-feed" function formatDate(iso: string | null): string { @@ -10,44 +10,40 @@ function formatDate(iso: string | null): string { export function FeedItemCard({ item }: { item: FeedItemType }) { return ( -
+
{item.url ? ( {item.title} ) : ( -

{item.title}

+

{item.title}

)} {item.date && ( -
{item.topics.length > 0 && ( -
+
{item.topics.map((topic) => topic.url ? ( - - {topic.label} - + {topic.label} ) : ( - - {topic.label} - + {topic.label} ), )}
)} -

{item.source}

+

{item.source}

) } diff --git a/src/features/feed/components/feed-list.tsx b/src/features/feed/components/feed-list.tsx index 11411cc..eb5f931 100644 --- a/src/features/feed/components/feed-list.tsx +++ b/src/features/feed/components/feed-list.tsx @@ -1,3 +1,4 @@ +import { BlockTitle, List } from "konsta/react" import { useMemo } from "react" import type { FeedItem } from "../lib/assemble-feed" import { FeedEmpty } from "./feed-empty" @@ -20,26 +21,22 @@ export function FeedList({ items }: { items: FeedItem[] }) {
{upcoming.length > 0 && (
-

- Anstehende Abstimmungen -

-
+ Anstehende Abstimmungen + {upcoming.map((item) => ( ))} -
+
)} {past.length > 0 && (
-

- Vergangene Abstimmungen -

-
+ Vergangene Abstimmungen + {past.map((item) => ( ))} -
+
)}
diff --git a/src/features/politicians/components/politician-search.tsx b/src/features/politicians/components/politician-search.tsx index dcd4311..b588a2e 100644 --- a/src/features/politicians/components/politician-search.tsx +++ b/src/features/politicians/components/politician-search.tsx @@ -1,9 +1,7 @@ -import { Button } from "@/shared/components/ui/button" -import { Card } from "@/shared/components/ui/card" -import { Input } from "@/shared/components/ui/input" import { useFollows } from "@/shared/hooks/use-follows" import type { MandateWithPolitician } from "@/shared/lib/aw-api" import { Link } from "@tanstack/react-router" +import { Block, Button, Card, List, ListItem, Navbar, Searchbar } from "konsta/react" import { useEffect, useMemo, useState } from "react" import { type GeoResult, loadCachedResult } from "../../location/lib/geo" import { getPartyMeta } from "../../location/lib/parties" @@ -16,7 +14,6 @@ interface PartyGroup { /** Extract party/fraction label for grouping. Prefers party, falls back to current fraction. */ function partyLabel(m: MandateWithPolitician): string { if (m.party?.label) return m.party.label - // fraction label is e.g. "CDU/CSU (Bundestag 2025 - 2029)" — strip the parliament suffix const current = m.fraction_membership?.find((f) => !f.valid_until) if (current) return current.fraction.label.replace(/\s*\([^)]+\)\s*$/, "") return "parteilos" @@ -74,36 +71,38 @@ export function PoliticianSearch() { if (!result || result.mandates.length === 0) { return ( -
-

- No parliament members loaded yet. Detect your location in Settings first. -

- - Zu den Einstellungen - -
+ <> + + +

+ Noch keine Abgeordneten geladen. Erkenne zuerst deinen Standort in den Einstellungen. +

+ + Zu den Einstellungen + +
+ ) } return ( -
-
- setSearch(e.target.value)} - placeholder="Filter by name…" - aria-label="Filter representatives" - /> -
+ <> + -
+ ) => setSearch(e.target.value)} + onClear={() => setSearch("")} + placeholder="Name filtern…" + /> + +
{groups.map((group) => { const meta = getPartyMeta(group.partyLabel) return ( - +
{group.partyLabel} - {group.members.length} + {group.members.length}
-
    + {group.members.map((m) => { const followed = isFollowing("politician", m.politician.id) const fn = mandateFunction(m) return ( -
  • -
    -

    {m.politician.label}

    - {fn &&

    {fn}

    } -
    - -
  • + title={m.politician.label} + subtitle={fn} + after={ + + } + /> ) })} -
+
) })} {groups.length === 0 && search && ( -

No members matching "{search}"

+

Keine Abgeordneten für „{search}"

)}
-
+ ) } diff --git a/src/features/settings/components/notification-guide.tsx b/src/features/settings/components/notification-guide.tsx index b19ac7b..463e926 100644 --- a/src/features/settings/components/notification-guide.tsx +++ b/src/features/settings/components/notification-guide.tsx @@ -1,4 +1,4 @@ -import { Button } from "@/shared/components/ui/button" +import { Block, Button, Navbar, NavbarBackLink } from "konsta/react" interface NotificationGuideProps { onBack: () => void @@ -6,100 +6,87 @@ interface NotificationGuideProps { export function NotificationGuide({ onBack }: NotificationGuideProps) { return ( -
- + <> + } /> -

Benachrichtigungen einrichten

-

- Push-Benachrichtigungen funktionieren auf dem iPhone nur, wenn die App zum Homescreen hinzugefügt wurde. -

+ +

+ Push-Benachrichtigungen funktionieren auf dem iPhone nur, wenn die App zum Homescreen hinzugefügt wurde. +

-
    -
  1. - - 1 - -
    -

    Teilen-Menü öffnen

    -

    - Tippe auf das Teilen-Symbol{" "} - {" "} - in der Safari-Leiste unten. -

    -
    -
  2. +
      +
    1. + + 1 + +
      +

      Teilen-Menü öffnen

      +

      + Tippe auf das Teilen-Symbol{" "} + {" "} + in der Safari-Leiste unten. +

      +
      +
    2. -
    3. - - 2 - -
      -

      Zum Home-Bildschirm

      -

      - Scrolle im Menü nach unten und wähle{" "} - Zum Home-Bildschirm. -

      -
      -
    4. +
    5. + + 2 + +
      +

      Zum Home-Bildschirm

      +

      + Scrolle im Menü nach unten und wähle Zum Home-Bildschirm. +

      +
      +
    6. -
    7. - - 3 - -
      -

      App hinzufügen

      -

      - Bestätige mit Hinzufügen. Die App erscheint als Icon - auf deinem Homescreen. -

      -
      -
    8. +
    9. + + 3 + +
      +

      App hinzufügen

      +

      + Bestätige mit Hinzufügen. Die App erscheint als Icon auf deinem + Homescreen. +

      +
      +
    10. -
    11. - - 4 - -
      -

      App öffnen & Benachrichtigungen aktivieren

      -

      - Öffne die App vom Homescreen aus und aktiviere die Benachrichtigungen in den Einstellungen. -

      -
      -
    12. -
    +
  3. + + 4 + +
    +

    App öffnen & Benachrichtigungen aktivieren

    +

    + Öffne die App vom Homescreen aus und aktiviere die Benachrichtigungen in den Einstellungen. +

    +
    +
  4. +
-
- -
-
+
+ +
+ + ) } diff --git a/src/features/settings/components/settings-page.tsx b/src/features/settings/components/settings-page.tsx index c35fb6e..eaf5f3b 100644 --- a/src/features/settings/components/settings-page.tsx +++ b/src/features/settings/components/settings-page.tsx @@ -1,12 +1,10 @@ -import { Button } from "@/shared/components/ui/button" -import { Card, CardContent } from "@/shared/components/ui/card" -import { Switch } from "@/shared/components/ui/switch" import { useDeviceId } from "@/shared/hooks/use-device-id" import { useFollows } from "@/shared/hooks/use-follows" import { usePush } from "@/shared/hooks/use-push" import { usePwaUpdate } from "@/shared/hooks/use-pwa-update" import { fetchTopics } from "@/shared/lib/aw-api" import { BACKEND_URL, VAPID_PUBLIC_KEY } from "@/shared/lib/constants" +import { BlockTitle, Button, List, ListItem, Navbar, Preloader, Toggle } from "konsta/react" import { useCallback, useEffect, useState } from "react" import { type GeoResult, clearGeoCache, detectFromCoords, loadCachedResult } from "../../location/lib/geo" import { NotificationGuide } from "./notification-guide" @@ -96,325 +94,241 @@ export function SettingsPage() { } return ( -
+
+ + {/* --- Notifications --- */} {VAPID_PUBLIC_KEY && ( -
-

- Benachrichtigungen -

- - - {push.permission === "denied" ? ( -
-

+ <> + Benachrichtigungen + + {push.permission === "denied" ? ( + Benachrichtigungen sind blockiert. Bitte in den Systemeinstellungen aktivieren. -

-
- ) : ( -
- - + } + /> + ) : ( + { - if (checked) push.subscribe() - else push.unsubscribe() + onChange={() => { + if (push.subscribed) push.unsubscribe() + else push.subscribe() }} /> -
- )} - {!standalone && ( - <> -
- - - )} - - -
+ } + /> + )} + {!standalone && setShowGuide(true)} />} + + )} {/* --- Location --- */} -
-

Standort

+ Standort + {hasLocation && result.bundesland ? ( + + + {result.landtag_label && } + + {result.cachedAt && } + + ) : null} - {hasLocation && result.bundesland ? ( - - -
-
- Bundesland - {result.bundesland} -
-
- {result.landtag_label && ( - <> -
-
-
- Landtag - {result.landtag_label} -
-
- - )} -
-
-
- Abgeordnete geladen - {result.mandates.length} -
-
- {result.cachedAt && ( - <> -
-
-
- Zwischengespeichert - {formatCacheAge(result.cachedAt)} -
-
- - )} - - - ) : null} - - {loading && ( - -
- {hasLocation ? "Aktualisiere…" : "Erkenne…"} - - )} - - {errorMsg && ( -
- {errorMsg} -
- )} - -
- {!hasLocation && !loading && ( - - )} - {hasLocation && ( - <> - - - - )} + {loading && ( +
+ + + {hasLocation ? "Aktualisiere…" : "Erkenne…"} +
-
+ )} + + {errorMsg && ( +
+ {errorMsg} +
+ )} + +
+ {!hasLocation && !loading && ( + + )} + {hasLocation && ( + <> + + + + )} +
{/* --- App Update --- */} -
-

App-Update

- - - {needRefresh ? ( - <> -
-
- Neue Version verfügbar - -
-
- - ) : ( - <> -
-
- App ist aktuell - -
-
- - )} -
-
-
+ App-Update + + {needRefresh ? ( + + Jetzt aktualisieren + + } + /> + ) : ( + + {checking ? "Prüfe…" : "Prüfen"} + + } + /> + )} + {/* --- About --- */} -
-

Info

- - -
-
- Datenquelle - - abgeordnetenwatch.de - -
-
-
-
-
- Geräte-ID - {deviceId} -
-
- - -
+ Info + + + abgeordnetenwatch.de + + } + /> + {deviceId}} + /> + {/* --- Developer --- */} -
-

Entwickler

- - - {/* Backend Health */} -
- Backend Health -
- {devHealth && ( - - {devHealth} - - )} - -
+ Entwickler + + + {devHealth && ( + {devHealth} + )} +
- -
- - {/* Test Push */} -
- Test-Benachrichtigung -
- {devPush && ( - - {devPush} - - )} - -
+ } + /> + + {devPush && ( + {devPush} + )} +
- -
- - {/* Follow all topics */} -
- Alle Themen folgen -
- {devTopics && {devTopics}} - -
+ } + /> + + {devTopics && {devTopics}} +
- -
- - {/* Follow all politicians */} -
- Alle Abgeordnete folgen -
- {devPoliticians && {devPoliticians}} - -
+ } + /> + + {devPoliticians && {devPoliticians}} +
- - -
-
+ } + /> + + ) } diff --git a/src/features/topics/components/topic-list.tsx b/src/features/topics/components/topic-list.tsx index 1e3a285..4a26a02 100644 --- a/src/features/topics/components/topic-list.tsx +++ b/src/features/topics/components/topic-list.tsx @@ -1,6 +1,5 @@ -import { Button } from "@/shared/components/ui/button" -import { Input } from "@/shared/components/ui/input" import { useFollows } from "@/shared/hooks/use-follows" +import { Button, List, ListItem, Navbar, Preloader, Searchbar } from "konsta/react" import { useState } from "react" import { useTopics } from "../hooks/use-topics" @@ -12,49 +11,51 @@ export function TopicList() { const filtered = topics.filter((t) => t.label.toLowerCase().includes(search.toLowerCase())) return ( -
-
- setSearch(e.target.value)} - placeholder="Search topics…" - aria-label="Search topics" - /> -
+ <> + + + ) => setSearch(e.target.value)} + onClear={() => setSearch("")} + placeholder="Themen suchen…" + /> {loading && ( - -
- +
+ +
)} {error && ( -
+
{error}
)} -
    + {filtered.map((topic) => { const followed = isFollowing("topic", topic.id) return ( -
  • - {topic.label} - -
  • + (followed ? unfollow("topic", topic.id) : follow("topic", topic.id, topic.label))} + aria-pressed={followed} + aria-label={followed ? `${topic.label} entfolgen` : `${topic.label} folgen`} + > + {followed ? "Folgst du" : "Folgen"} + + } + /> ) })} -
-
+ + ) }