feat: add reusable ErrorView and LoadingView components
Extract duplicated inline error/loading patterns from TaskListScreen, MyTasksScreen, BusinessSelectionScreen, AccountScreen, and TaskDetailScreen into shared components under Views/Components/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
88a3a9d6e0
commit
1c427a0902
7 changed files with 71 additions and 75 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
34
PayfritWorks/Views/Components/ErrorView.swift
Normal file
34
PayfritWorks/Views/Components/ErrorView.swift
Normal file
|
|
@ -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.")
|
||||
}
|
||||
27
PayfritWorks/Views/Components/LoadingView.swift
Normal file
27
PayfritWorks/Views/Components/LoadingView.swift
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue