- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.1k
Add a banner to ask user to validate their email #24172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            8 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      fec8da8
              
                Add a banner to ask user to validate their email
              
              
                crazytonyli a15629b
              
                Address PR comments
              
              
                crazytonyli 5090d68
              
                Replace Spacer() with .frame
              
              
                crazytonyli 8677474
              
                Update verify email cell UI
              
              
                crazytonyli 5510b26
              
                Update variables
              
              
                crazytonyli 97e9327
              
                Update UI to be similar to the web
              
              
                crazytonyli 0760a08
              
                Merge branch 'trunk' into verify-email-address
              
              
                crazytonyli b640cfb
              
                Fix typos
              
              
                crazytonyli File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
        
          
          
            186 changes: 186 additions & 0 deletions
          
          186 
        
  WordPress/Classes/ViewRelated/Me/Me Main/VerifyEmailRow.swift
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,186 @@ | ||
| import Foundation | ||
| import UIKit | ||
| import SwiftUI | ||
| import WordPressUI | ||
| import Combine | ||
|  | ||
| final class VerifyEmailRow: ImmuTableRow { | ||
| static let cell = ImmuTableCell.class(VerifyEmailCell.self) | ||
| let action: ImmuTableAction? = nil | ||
|  | ||
| func configureCell(_ cell: UITableViewCell) { | ||
| // Do nothing. | ||
| } | ||
| } | ||
|  | ||
| final class VerifyEmailCell: UITableViewCell { | ||
| private let hostingView: UIHostingView<VerifyEmailView> | ||
|  | ||
| override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { | ||
| hostingView = .init(view: .init()) | ||
| super.init(style: style, reuseIdentifier: reuseIdentifier) | ||
| setupView() | ||
| } | ||
|  | ||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|  | ||
| private func setupView() { | ||
| selectionStyle = .none | ||
|  | ||
| contentView.addSubview(hostingView) | ||
| hostingView.pinEdges(to: contentView) | ||
| } | ||
| } | ||
|  | ||
| private struct VerifyEmailView: View { | ||
| @StateObject private var viewModel = VerifyEmailViewModel() | ||
|  | ||
| var body: some View { | ||
| VStack(alignment: .leading, spacing: 8) { | ||
| HStack { | ||
| Image(systemName: "envelope.circle.fill") | ||
| Text(Strings.verifyEmailTitle) | ||
| } | ||
| .foregroundStyle(Color(uiColor: #colorLiteral(red: 0.8392476439, green: 0.2103677094, blue: 0.2182099223, alpha: 1))) | ||
| .font(.subheadline.weight(.semibold)) | ||
|  | ||
| Text(viewModel.state.message) | ||
| .font(.callout) | ||
| .foregroundStyle(.primary) | ||
|  | ||
| Spacer() | ||
|  | ||
| Button { | ||
| viewModel.sendVerificationEmail() | ||
| } label: { | ||
| HStack { | ||
| if viewModel.state.showsActivityIndicator { | ||
| ProgressView() | ||
| .progressViewStyle(CircularProgressViewStyle()) | ||
| } | ||
|  | ||
| Text(viewModel.state.buttonTitle) | ||
| .font(.callout) | ||
| } | ||
| } | ||
| .buttonStyle(.borderless) | ||
| .disabled(!viewModel.state.isButtonEnabled) | ||
| } | ||
| .padding() | ||
| .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) | ||
| } | ||
| } | ||
|  | ||
| // This value is not an actual "timeout" value of the verification link. It's just an arbitrary value to prevent | ||
| // users from sending links repeatedly. | ||
| private let verificationLinkTimeout: TimeInterval = 300 | ||
|  | ||
| @MainActor | ||
| private class VerifyEmailViewModel: ObservableObject { | ||
| enum State { | ||
| case needsVerification | ||
| case sending | ||
| case sent(Date) | ||
| case error(Error) | ||
|  | ||
| var message: String { | ||
| let email = try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext)?.email ?? "" | ||
|  | ||
| switch self { | ||
| case .needsVerification, .sending, .sent: | ||
| if let email, !email.isEmpty { | ||
| return String(format: Strings.verifyMessage, email, Strings.sendButton) | ||
| } else { | ||
| return String(format: Strings.verifyMessageNoEmail, Strings.sendButton) | ||
| } | ||
| case .error(let error): | ||
| return error.localizedDescription | ||
| } | ||
| } | ||
|  | ||
| var buttonTitle: String { | ||
| switch self { | ||
| case .needsVerification: | ||
| return Strings.sendButton | ||
| case .sending: | ||
| return Strings.sendingButton | ||
| case .sent: | ||
| return Strings.sentButton | ||
| case .error: | ||
| return Strings.retryButton | ||
| } | ||
| } | ||
|  | ||
| var isButtonEnabled: Bool { | ||
| switch self { | ||
| case .needsVerification, .error: return true | ||
| case .sending: return false | ||
| case .sent(let date): | ||
| return Date().timeIntervalSince(date) >= verificationLinkTimeout | ||
| } | ||
| } | ||
|  | ||
| var showsActivityIndicator: Bool { | ||
| if case .sending = self { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
| } | ||
|  | ||
| private let userID: NSNumber | ||
|  | ||
| private var lastVerificationSentDate: Date? { | ||
| get { | ||
| let key = "LastEmailVerificationSentDate-\(userID)" | ||
| return UserDefaults.standard.object(forKey: key) as? Date | ||
|         
                  kean marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| } | ||
| set { | ||
| let key = "LastEmailVerificationSentDate-\(userID)" | ||
| UserDefaults.standard.set(newValue, forKey: key) | ||
| } | ||
| } | ||
|  | ||
| @Published private(set) var state: State | ||
|  | ||
| init() { | ||
| userID = (try? WPAccount.lookupDefaultWordPressComAccount(in: ContextManager.shared.mainContext)?.userID) ?? 0 | ||
| state = .needsVerification | ||
|  | ||
| if let sentDate = lastVerificationSentDate, | ||
| Date().timeIntervalSince(sentDate) < verificationLinkTimeout { | ||
| state = .sent(sentDate) | ||
| } | ||
| } | ||
|  | ||
| func sendVerificationEmail() { | ||
| guard state.isButtonEnabled else { return } | ||
|  | ||
| state = .sending | ||
|  | ||
| let accountService = AccountService(coreDataStack: ContextManager.shared) | ||
| accountService.requestVerificationEmail({ [weak self] in | ||
| Task { @MainActor [weak self] in | ||
| guard let self else { return } | ||
| self.lastVerificationSentDate = Date() | ||
| self.state = .sent(Date()) | ||
| } | ||
| }, failure: { [weak self] error in | ||
| Task { @MainActor [weak self] in | ||
| self?.state = .error(error) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|  | ||
| private enum Strings { | ||
| static let verifyEmailTitle = NSLocalizedString("me.verifyEmail.title", value: "Verify Your Email", comment: "Title for email verification card") | ||
| static let verifyMessage = NSLocalizedString("me.verifyEmail.message.withEmail", value: "Verify your email to secure your account and access more features.\nCheck your inbox at %@ for the confirmation email, or click '%@' to get a new one.", comment: "Message for email verification card with email address") | ||
| static let verifyMessageNoEmail = NSLocalizedString("me.verifyEmail.message.noEmail", value: "Verify your email to secure your account and access more features.\nCheck your inbox for the confirmation email, or click '%@' to get a new one..", comment: "Message for email verification card") | ||
| static let sendButton = NSLocalizedString("me.verifyEmail.button.send", value: "Resend email", comment: "Button title to send verification link") | ||
| static let sendingButton = NSLocalizedString("me.verifyEmail.button.sending", value: "Sending...", comment: "Button title while verification link is being sent") | ||
| static let sentButton = NSLocalizedString("me.verifyEmail.button.sent", value: "Email sent", comment: "Button title after verification link is sent") | ||
| static let retryButton = NSLocalizedString("me.verifyEmail.button.retry", value: "Try Again", comment: "Button title when verification link sending failed") | ||
| } | ||
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a bit on a high end. I'd go with 60 seconds, perhaps?
I'd suggest showing the timer in the UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think showing a countdown on the UI is not necessary? Showing a countdown feels like it's an action that the user needs to take action right now, but there is no real urgency in clicking the email link.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The app could start showing it if you tap "Resend" before it's possible. This way you'll know when you can tap it again. If that's what it already does – please ignore it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no real restrictions on the endpoint to limit when sending another email is possible.
BTW, I checked the website UI. It neither show a timer nor prevent users from keep resending emails. Once user refresh the webpage, they can send another one.
I pushed another commit to follow the web UI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if it's relatively free, it's should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'll keep it the same with the web for now.