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
|
// Payout content
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
LoadingView()
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
errorView(error)
|
ErrorView(message: error) { Task { await loadData() } }
|
||||||
} else {
|
} else {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
if let tier = tierStatus {
|
if let tier = tierStatus {
|
||||||
|
|
@ -386,21 +386,6 @@ struct AccountScreen: View {
|
||||||
.padding(.top, 8)
|
.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
|
// MARK: - Actions
|
||||||
|
|
||||||
private func loadAvatar() async {
|
private func loadAvatar() async {
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ struct BusinessSelectionScreen: View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Group {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
LoadingView()
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
errorView(error)
|
ErrorView(message: error) { loadBusinesses() }
|
||||||
} else if businesses.isEmpty {
|
} else if businesses.isEmpty {
|
||||||
emptyView
|
emptyView
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -246,19 +246,6 @@ struct BusinessSelectionScreen: View {
|
||||||
.padding()
|
.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
|
// MARK: - Actions
|
||||||
|
|
||||||
private func loadBusinesses(silent: Bool = false) {
|
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] ?? []
|
let tasks = tasksByFilter[filterType] ?? []
|
||||||
|
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
LoadingView()
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
VStack(spacing: 16) {
|
ErrorView(message: error) { loadTasks(filterType) }
|
||||||
Image(systemName: "exclamationmark.circle")
|
|
||||||
.font(.system(size: 48))
|
|
||||||
.foregroundColor(.red)
|
|
||||||
Text(error)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
Button("Retry") { loadTasks(filterType) }
|
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
} else if tasks.isEmpty {
|
} else if tasks.isEmpty {
|
||||||
emptyView(filterType)
|
emptyView(filterType)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,9 @@ struct TaskDetailScreen: View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Group {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
LoadingView()
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
errorView(error)
|
ErrorView(message: error) { Task { await loadDetails() } }
|
||||||
} else if let details = details {
|
} else if let details = details {
|
||||||
contentView(details)
|
contentView(details)
|
||||||
}
|
}
|
||||||
|
|
@ -663,21 +663,6 @@ struct TaskDetailScreen: View {
|
||||||
.background(.ultraThinMaterial)
|
.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
|
// MARK: - Actions
|
||||||
|
|
||||||
private func loadDetails() async {
|
private func loadDetails() async {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ struct TaskListScreen: View {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
Group {
|
Group {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
LoadingView()
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
errorView(error)
|
ErrorView(message: error) { loadTasks() }
|
||||||
} else if tasks.isEmpty {
|
} else if tasks.isEmpty {
|
||||||
emptyView
|
emptyView
|
||||||
} else {
|
} 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
|
// MARK: - Actions
|
||||||
|
|
||||||
private func loadTasks(silent: Bool = false) {
|
private func loadTasks(silent: Bool = false) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue