wordeck-native/Sources/Features/Account/AccountView.swift
Till JS 542082772a refactor(big-bang): cards-native → wordeck-native
Code + Identity-Rename zur Vorbereitung auf Apple-Dev-Portal-Aktion
(Bundle ev.mana.wordeck, App-Group group.ev.mana.wordeck, AASA
applinks:wordeck.com). Build bleibt funktional, aber gegen die
neue text-only-API können image-occlusion-Creates 422 zurückgeben —
das wird mit der Wordeck-Native v1.0-Welle (parallele Apple-Aktion)
sauber gemacht.

Umbenennung:
- 41 Files: cardecky/Cardecky → wordeck/Wordeck (Display, Strings,
  Kommentare)
- 57 Files: CardsNative → WordeckNative, CardsAPI → WordeckAPI,
  CardsTheme → WordeckTheme, CardsBrand → WordeckBrand, CardsWidget →
  WordeckWidget, CardsDueWidget → WordeckDueWidget
- Bundle-ID ev.mana.cardecky → ev.mana.wordeck (project.yml,
  Info.plist, entitlements, Keychain-Service, App-Group)
- AASA applinks:cardecky.mana.how → applinks:wordeck.com
- API-Base cardecky-api.mana.how → api.wordeck.com
- 10 Files renamed (App-Entry, API-Extensions, Theme, Widget,
  Entitlements, Tests)
- xcodeproj regenerated via xcodegen → WordeckNative.xcodeproj
- MaskRegionsTests.swift gelöscht (image-occlusion entfällt mit
  Wordeck text-only)

Forgejo-Repo git.mana.how/till/cards-native → wordeck-native umbenannt
(Auto-Redirect aktiv). Lokales Verzeichnis Code/cards-native/ bleibt
vorerst — wird beim nächsten Apple-Setup mit Bundle-Test umbenannt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 23:10:42 +02:00

200 lines
7.1 KiB
Swift

import ManaAuthUI
import ManaCore
import SwiftUI
struct AccountView: View {
@Environment(AuthClient.self) private var auth
@Environment(ManaAuthGate.self) private var authGate
@State private var showChangeEmail = false
@State private var showChangePassword = false
@State private var showDeleteAccount = false
var body: some View {
ZStack {
WordeckTheme.background.ignoresSafeArea()
Group {
switch auth.status {
case .signedIn:
signedInContent
case .guest, .signedOut, .error, .unknown:
guestContent
case .signingIn, .twoFactorRequired:
ProgressView().tint(WordeckTheme.primary)
}
}
}
.navigationTitle("Account")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.manaBrand(WordeckBrand.manaBrand)
.sheet(isPresented: $showChangeEmail) {
ManaChangeEmailView(
auth: auth,
callbackUniversalLink: URL(string: "https://wordeck.com/auth/email-changed"),
onDone: { showChangeEmail = false }
)
.manaBrand(WordeckBrand.manaBrand)
}
.sheet(isPresented: $showChangePassword) {
ManaChangePasswordView(
auth: auth,
onDone: { showChangePassword = false }
)
.manaBrand(WordeckBrand.manaBrand)
}
.sheet(isPresented: $showDeleteAccount) {
ManaDeleteAccountView(
auth: auth,
onDone: { showDeleteAccount = false }
)
.manaBrand(WordeckBrand.manaBrand)
}
}
private var signedInContent: some View {
VStack(spacing: 20) {
Image(systemName: "person.crop.circle.fill")
.resizable()
.frame(width: 80, height: 80)
.foregroundStyle(WordeckTheme.primary)
if let email = auth.currentEmail {
Text(email)
.font(.headline)
.foregroundStyle(WordeckTheme.foreground)
}
VStack(spacing: 12) {
NavigationLink {
SettingsView()
} label: {
rowLabel("Einstellungen", systemImage: "gear")
}
.buttonStyle(.plain)
Button { showChangeEmail = true } label: {
rowLabel("Email ändern", systemImage: "envelope")
}
.buttonStyle(.plain)
Button { showChangePassword = true } label: {
rowLabel("Passwort ändern", systemImage: "key")
}
.buttonStyle(.plain)
ManaTwoFactorAccountRow(auth: auth)
.padding(.vertical, 12)
.padding(.horizontal, 16)
.background(WordeckTheme.surface, in: RoundedRectangle(cornerRadius: 8))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(WordeckTheme.border, lineWidth: 1)
)
}
.padding(.horizontal, 32)
Spacer()
Button(role: .destructive) {
// Logout behält die Guest-Identity App bleibt im
// anonymen Modus nutzbar (lokale Decks, Marketplace
// browsen). Wer alles vergessen" will, nutzt
// Account löschen".
Task { await auth.signOut(keepGuestMode: true) }
} label: {
Text("Abmelden")
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(WordeckTheme.error.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
.foregroundStyle(WordeckTheme.error)
}
.padding(.horizontal, 32)
// App-Store-Guideline 5.1.1(v): jede App mit Sign-Up MUSS
// eine Account-Löschung anbieten.
Button(role: .destructive) {
showDeleteAccount = true
} label: {
Text("Account löschen…")
.font(.footnote)
.foregroundStyle(WordeckTheme.mutedForeground)
}
.padding(.bottom, 16)
}
.padding(.top, 48)
}
private var guestContent: some View {
VStack(spacing: 20) {
Image(systemName: "person.crop.circle.dashed")
.resizable()
.frame(width: 80, height: 80)
.foregroundStyle(WordeckTheme.mutedForeground)
VStack(spacing: 8) {
Text("Du nutzt Wordeck anonym")
.font(.headline)
.foregroundStyle(WordeckTheme.foreground)
Text(
"""
Marketplace und lokale Decks funktionieren ohne Konto. \
Für KI-Karten, eigene Decks im Cloud-Sync und Marketplace-\
Veröffentlichung brauchst du ein Konto.
"""
)
.font(.subheadline)
.foregroundStyle(WordeckTheme.mutedForeground)
.multilineTextAlignment(.center)
}
.padding(.horizontal, 32)
VStack(spacing: 12) {
Button {
// Trigger ohne pending-Action wir wollen einfach
// das Sign-In-Sheet öffnen. `require` mit no-op
// schaltet die Sheet-Logik des Gates ein.
authGate.require(reason: "account-tab") {}
} label: {
Text("Anmelden / Konto erstellen")
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(WordeckTheme.primary, in: RoundedRectangle(cornerRadius: 10))
.foregroundStyle(.white)
}
.buttonStyle(.plain)
NavigationLink {
SettingsView()
} label: {
rowLabel("Einstellungen", systemImage: "gear")
}
.buttonStyle(.plain)
}
.padding(.horizontal, 32)
Spacer()
}
.padding(.top, 48)
}
private func rowLabel(_ title: String, systemImage: String) -> some View {
Label(title, systemImage: systemImage)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 12)
.padding(.horizontal, 16)
.background(WordeckTheme.surface, in: RoundedRectangle(cornerRadius: 8))
.foregroundStyle(WordeckTheme.foreground)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(WordeckTheme.border, lineWidth: 1)
)
}
}
#Preview {
NavigationStack {
AccountView()
.environment(AuthClient(config: AppConfig.manaAppConfig))
}
}