diff --git a/PayfritWorks/Views/AccountScreen.swift b/PayfritWorks/Views/AccountScreen.swift index 232107f..e876ccc 100644 --- a/PayfritWorks/Views/AccountScreen.swift +++ b/PayfritWorks/Views/AccountScreen.swift @@ -90,10 +90,10 @@ struct AccountScreen: View { // Payout content if isLoading { - ProgressView() + LoadingView() .padding(.top, 20) } else if let error = error { - errorView(error) + ErrorView(message: error) { Task { await loadData() } } } else { VStack(spacing: 16) { if let tier = tierStatus { @@ -386,21 +386,6 @@ struct AccountScreen: View { .padding(.top, 8) } - // MARK: - Error - - private func errorView(_ msg: String) -> some View { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.circle") - .font(.system(size: 48)) - .foregroundColor(.red) - Text(msg) - .multilineTextAlignment(.center) - Button("Retry") { Task { await loadData() } } - .buttonStyle(.borderedProminent) - } - .padding() - } - // MARK: - Actions private func loadAvatar() async { diff --git a/PayfritWorks/Views/BusinessSelectionScreen.swift b/PayfritWorks/Views/BusinessSelectionScreen.swift index 6921937..bb5a7d9 100644 --- a/PayfritWorks/Views/BusinessSelectionScreen.swift +++ b/PayfritWorks/Views/BusinessSelectionScreen.swift @@ -20,9 +20,9 @@ struct BusinessSelectionScreen: View { ZStack(alignment: .bottomTrailing) { Group { if isLoading { - ProgressView() + LoadingView() } else if let error = error { - errorView(error) + ErrorView(message: error) { loadBusinesses() } } else if businesses.isEmpty { emptyView } else { @@ -246,19 +246,6 @@ struct BusinessSelectionScreen: View { .padding() } - private func errorView(_ message: String) -> some View { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.circle") - .font(.system(size: 48)) - .foregroundColor(.red) - Text(message) - .multilineTextAlignment(.center) - Button("Retry") { loadBusinesses() } - .buttonStyle(.borderedProminent) - } - .padding() - } - // MARK: - Actions private func loadBusinesses(silent: Bool = false) { diff --git a/PayfritWorks/Views/Components/ErrorView.swift b/PayfritWorks/Views/Components/ErrorView.swift new file mode 100644 index 0000000..72000e9 --- /dev/null +++ b/PayfritWorks/Views/Components/ErrorView.swift @@ -0,0 +1,34 @@ +import SwiftUI + +/// Reusable error state view with icon, message, and optional retry button. +/// Replaces inline error patterns across all screens. +struct ErrorView: View { + let message: String + var onRetry: (() -> Void)? + + var body: some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.circle") + .font(.system(size: 48)) + .foregroundColor(.red) + Text(message) + .multilineTextAlignment(.center) + .foregroundColor(.primary) + if let onRetry = onRetry { + Button("Retry", action: onRetry) + .buttonStyle(.borderedProminent) + } + } + .padding() + } +} + +#Preview("With Retry") { + ErrorView(message: "Something went wrong. Please try again.") { + print("Retry tapped") + } +} + +#Preview("No Retry") { + ErrorView(message: "Could not load data.") +} diff --git a/PayfritWorks/Views/Components/LoadingView.swift b/PayfritWorks/Views/Components/LoadingView.swift new file mode 100644 index 0000000..def15c6 --- /dev/null +++ b/PayfritWorks/Views/Components/LoadingView.swift @@ -0,0 +1,27 @@ +import SwiftUI + +/// Reusable loading state view with a spinner and optional message. +/// Replaces inline ProgressView() patterns across all screens. +struct LoadingView: View { + var message: String? + + var body: some View { + VStack(spacing: 12) { + ProgressView() + if let message = message { + Text(message) + .font(.subheadline) + .foregroundColor(.secondary) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +#Preview("With Message") { + LoadingView(message: "Loading tasks...") +} + +#Preview("No Message") { + LoadingView() +} diff --git a/PayfritWorks/Views/MyTasksScreen.swift b/PayfritWorks/Views/MyTasksScreen.swift index 661132c..5b33cbe 100644 --- a/PayfritWorks/Views/MyTasksScreen.swift +++ b/PayfritWorks/Views/MyTasksScreen.swift @@ -94,18 +94,9 @@ struct MyTasksScreen: View { let tasks = tasksByFilter[filterType] ?? [] if isLoading { - ProgressView() + LoadingView() } else if let error = error { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.circle") - .font(.system(size: 48)) - .foregroundColor(.red) - Text(error) - .multilineTextAlignment(.center) - Button("Retry") { loadTasks(filterType) } - .buttonStyle(.borderedProminent) - } - .padding() + ErrorView(message: error) { loadTasks(filterType) } } else if tasks.isEmpty { emptyView(filterType) } else { diff --git a/PayfritWorks/Views/TaskDetailScreen.swift b/PayfritWorks/Views/TaskDetailScreen.swift index f5a56c4..0050cf3 100644 --- a/PayfritWorks/Views/TaskDetailScreen.swift +++ b/PayfritWorks/Views/TaskDetailScreen.swift @@ -42,9 +42,9 @@ struct TaskDetailScreen: View { ZStack(alignment: .bottomTrailing) { Group { if isLoading { - ProgressView() + LoadingView() } else if let error = error { - errorView(error) + ErrorView(message: error) { Task { await loadDetails() } } } else if let details = details { contentView(details) } @@ -663,21 +663,6 @@ struct TaskDetailScreen: View { .background(.ultraThinMaterial) } - // MARK: - Error - - private func errorView(_ message: String) -> some View { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.circle") - .font(.system(size: 48)) - .foregroundColor(.red) - Text(message) - .multilineTextAlignment(.center) - Button("Retry") { Task { await loadDetails() } } - .buttonStyle(.borderedProminent) - } - .padding() - } - // MARK: - Actions private func loadDetails() async { diff --git a/PayfritWorks/Views/TaskListScreen.swift b/PayfritWorks/Views/TaskListScreen.swift index 2cfc565..317ef6c 100644 --- a/PayfritWorks/Views/TaskListScreen.swift +++ b/PayfritWorks/Views/TaskListScreen.swift @@ -17,9 +17,9 @@ struct TaskListScreen: View { ZStack(alignment: .bottomTrailing) { Group { if isLoading { - ProgressView() + LoadingView() } else if let error = error { - errorView(error) + ErrorView(message: error) { loadTasks() } } else if tasks.isEmpty { emptyView } else { @@ -195,19 +195,6 @@ struct TaskListScreen: View { } } - private func errorView(_ message: String) -> some View { - VStack(spacing: 16) { - Image(systemName: "exclamationmark.circle") - .font(.system(size: 48)) - .foregroundColor(.red) - Text(message) - .multilineTextAlignment(.center) - Button("Retry") { loadTasks() } - .buttonStyle(.borderedProminent) - } - .padding() - } - // MARK: - Actions private func loadTasks(silent: Bool = false) {