ErrorView.swift (4729B)
1 // 2 // ErrorView.swift 3 // damus 4 // 5 // Created by Daniel D'Aquino on 2025-01-08. 6 // 7 8 import SwiftUI 9 10 /// A generic user-presentable error view 11 /// 12 /// Use this to handle and display errors to the user when it does not make sense to create a custom error view. 13 /// This includes good error handling UX practices, such as: 14 /// - Clear description 15 /// - Actionable advice for the user on what to do next. 16 /// - One-click support contact options 17 struct ErrorView: View { 18 let damus_state: DamusState? 19 let error: UserPresentableError 20 21 @Environment(\.dismiss) var dismiss 22 23 var body: some View { 24 VStack(spacing: 6) { 25 Image(systemName: "exclamationmark.circle") 26 .resizable() 27 .frame(width: 30, height: 30) 28 .foregroundStyle(.red) 29 .accessibilityHidden(true) 30 Text("Oops!", comment: "Heading for an error screen") 31 .font(.title) 32 .bold() 33 .padding(.bottom, 10) 34 .accessibilityHeading(.h1) 35 Text(error.user_visible_description) 36 .foregroundStyle(.secondary) 37 38 VStack(alignment: .leading, spacing: 6) { 39 HStack(spacing: 5) { 40 Image(systemName: "sparkles") 41 .accessibilityHidden(true) 42 Text("Advice", comment: "Heading for some advice text to help the user with an error") 43 .font(.headline) 44 .accessibilityHeading(.h3) 45 } 46 Text(error.tip) 47 } 48 .padding() 49 .background(Color.secondary.opacity(0.2)) 50 .cornerRadius(10) 51 .padding(.vertical, 30) 52 53 Spacer() 54 55 if let damus_state, damus_state.is_privkey_user { 56 Button(action: { 57 damus_state.nav.push(route: .DMChat(dms: .init(our_pubkey: damus_state.keypair.pubkey, pubkey: Constants.SUPPORT_PUBKEY))) 58 dismiss() 59 }, label: { 60 Text("Contact support via DMs", comment: "Button label to contact support from an error screen") 61 }) 62 .padding(.vertical, 20) 63 } 64 Text("Contact support via email at [support@damus.io](mailto:support@damus.io)", comment: "Text to contact support via email") 65 .font(.caption) 66 .foregroundStyle(.secondary) 67 } 68 .padding(20) 69 .padding(.top, 20) 70 } 71 72 /// An error that is displayed to the user, and can be sent to the Developers as well. 73 struct UserPresentableError { 74 /// The description of the error to be shown to the user 75 /// 76 /// **Requirements:** 77 /// - This should not be technical. It should use accessible language 78 /// - Should be localized 79 /// - It should try to explain the user what happened, and — if possible — why. 80 let user_visible_description: String 81 82 /// Helpful tip/advice to the user, to help them overcome the error 83 /// 84 /// **Requirements:** 85 /// - Should provide actionable advice to the user 86 /// - This should not be overly technical 87 /// - Should be localized 88 /// - Should NOT include support contact (The view that will display this will already include support contact options) 89 /// 90 /// **Implementation notes:** 91 /// - This is NOT an optional value, because part of good UX is making sure error messages are actionable, which is something that is often forgotten. It's not uncommon for error messages to be written in vague, technical, and/or unactionable terms, but this is when the user needs help the most. And so this field is made mandatory to force developers to write actionable content to the user 92 let tip: String 93 94 /// Technical information about the error, which will be sendable to developers 95 /// 96 /// Note: This is still unutilized, but this will be used in the future. 97 /// 98 /// **Requirements** 99 /// - Should never include any sensitive info 100 /// - Should be in English. The developers are the main audience. 101 /// - Should include helping info, such as context in which the error happens. 102 /// - Should be technical 103 let technical_info: String? 104 105 } 106 } 107 108 109 110 #Preview { 111 ErrorView( 112 damus_state: test_damus_state, 113 error: .init( 114 user_visible_description: "We are still too early", 115 tip: "Stay humble, keep building, stack sats", 116 technical_info: nil 117 ) 118 ) 119 }