Skip to content

Commit

Permalink
[StripeConnect] Onboarding settings. (#4039)
Browse files Browse the repository at this point in the history
  • Loading branch information
nschris-stripe authored Sep 25, 2024
1 parent e997557 commit d94be4c
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
413D184A2C7FCE5E0051AA42 /* StripeConnectExample.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = 413D18492C7FCE5E0051AA42 /* StripeConnectExample.xctestplan */; };
4161C2892CA1B438005BD67C /* StripeUICore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4161C2882CA1B438005BD67C /* StripeUICore.framework */; };
4161C28A2CA1B438005BD67C /* StripeUICore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4161C2882CA1B438005BD67C /* StripeUICore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4161C2822C9DBB92005BD67C /* OnboardingSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4161C2812C9DBB92005BD67C /* OnboardingSettingsView.swift */; };
4161C2852C9DC235005BD67C /* OnboardingSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4161C2842C9DC235005BD67C /* OnboardingSettings.swift */; };
4161C2872C9DD035005BD67C /* URL+Valid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4161C2862C9DD035005BD67C /* URL+Valid.swift */; };
416E9E972C76BD4400A0B917 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9E962C76BD4400A0B917 /* AppDelegate.swift */; };
416E9E992C76BD4400A0B917 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9E982C76BD4400A0B917 /* SceneDelegate.swift */; };
416E9E9B2C76BD4400A0B917 /* AppStartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 416E9E9A2C76BD4400A0B917 /* AppStartViewController.swift */; };
Expand Down Expand Up @@ -73,6 +76,9 @@
413D18432C7FAE280051AA42 /* OptionListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionListRow.swift; sourceTree = "<group>"; };
413D18492C7FCE5E0051AA42 /* StripeConnectExample.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = StripeConnectExample.xctestplan; sourceTree = "<group>"; };
4161C2882CA1B438005BD67C /* StripeUICore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = StripeUICore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4161C2812C9DBB92005BD67C /* OnboardingSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSettingsView.swift; sourceTree = "<group>"; };
4161C2842C9DC235005BD67C /* OnboardingSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSettings.swift; sourceTree = "<group>"; };
4161C2862C9DD035005BD67C /* URL+Valid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Valid.swift"; sourceTree = "<group>"; };
416E9E932C76BD4400A0B917 /* StripeConnect Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "StripeConnect Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
416E9E962C76BD4400A0B917 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
416E9E982C76BD4400A0B917 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,6 +144,33 @@
path = Views;
sourceTree = "<group>";
};
4161C27F2C9DBB70005BD67C /* Settings */ = {
isa = PBXGroup;
children = (
4161C2802C9DBB7E005BD67C /* Components */,
41E9A1C02C7CE30000EDE131 /* AppSettingsView.swift */,
41810D742C8A004F00F10EB7 /* Appearance */,
);
path = Settings;
sourceTree = "<group>";
};
4161C2802C9DBB7E005BD67C /* Components */ = {
isa = PBXGroup;
children = (
4161C2832C9DC1CC005BD67C /* Onboarding */,
);
path = Components;
sourceTree = "<group>";
};
4161C2832C9DC1CC005BD67C /* Onboarding */ = {
isa = PBXGroup;
children = (
4161C2812C9DBB92005BD67C /* OnboardingSettingsView.swift */,
4161C2842C9DC235005BD67C /* OnboardingSettings.swift */,
);
path = Onboarding;
sourceTree = "<group>";
};
416E9E8A2C76BD4400A0B917 = {
isa = PBXGroup;
children = (
Expand All @@ -162,7 +195,7 @@
416E9E952C76BD4400A0B917 /* StripeConnectExample */ = {
isa = PBXGroup;
children = (
41810D742C8A004F00F10EB7 /* Appearance */,
4161C27F2C9DBB70005BD67C /* Settings */,
413D18402C7FA2FF0051AA42 /* Views */,
41E9A1BB2C7CC83500EDE131 /* Helpers */,
416E9EE12C7B987400A0B917 /* Storage */,
Expand All @@ -174,7 +207,6 @@
416E9EA12C76BD4600A0B917 /* LaunchScreen.storyboard */,
416E9EA42C76BD4600A0B917 /* Info.plist */,
416E9EDF2C78C10800A0B917 /* MainViewController.swift */,
41E9A1C02C7CE30000EDE131 /* AppSettingsView.swift */,
);
path = StripeConnectExample;
sourceTree = "<group>";
Expand Down Expand Up @@ -255,6 +287,7 @@
children = (
41E9A1BC2C7CC85100EDE131 /* SwiftUIContainerViewController.swift */,
41E9A1BE2C7CD3C700EDE131 /* UIViewController+NavigationEmbed.swift */,
4161C2862C9DD035005BD67C /* URL+Valid.swift */,
);
path = Helpers;
sourceTree = "<group>";
Expand Down Expand Up @@ -364,18 +397,21 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4161C2872C9DD035005BD67C /* URL+Valid.swift in Sources */,
416E9E9B2C76BD4400A0B917 /* AppStartViewController.swift in Sources */,
413D18442C7FAE280051AA42 /* OptionListRow.swift in Sources */,
41810D782C8A006F00F10EB7 /* AppSettings+AppearanceInfo.swift in Sources */,
416E9E972C76BD4400A0B917 /* AppDelegate.swift in Sources */,
41E9A1C12C7CE30000EDE131 /* AppSettingsView.swift in Sources */,
416E9ED62C78BD9900A0B917 /* API.swift in Sources */,
416E9E992C76BD4400A0B917 /* SceneDelegate.swift in Sources */,
4161C2822C9DBB92005BD67C /* OnboardingSettingsView.swift in Sources */,
416E9EDA2C78BDC300A0B917 /* AccountSessionResponse.swift in Sources */,
416E9EE32C7B988100A0B917 /* AppSettings.swift in Sources */,
416E9EDC2C78BE1300A0B917 /* APIError.swift in Sources */,
413D18422C7FA30A0051AA42 /* TextInput.swift in Sources */,
41E9A1BD2C7CC85100EDE131 /* SwiftUIContainerViewController.swift in Sources */,
4161C2852C9DC235005BD67C /* OnboardingSettings.swift in Sources */,
416E9EDE2C78C0BA00A0B917 /* AppInfo.swift in Sources */,
41810D762C8A005D00F10EB7 /* AppearanceInfo.swift in Sources */,
41810D712C89F4CD00F10EB7 /* AppearanceSettings.swift in Sources */,
Expand Down Expand Up @@ -548,6 +584,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -574,6 +611,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// URL+Valid.swift
// StripeConnect Example
//
// Created by Chris Mays on 9/20/24.
//

import UIKit

extension URL {
var isValid: Bool {
UIApplication.shared.canOpenURL(self)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,13 @@ class MainViewController: UITableViewController {

switch row {
case .onboarding:
viewControllerToPush = embeddedComponentManager.createAccountOnboardingViewController(fullTermsOfServiceUrl: nil, recipientTermsOfServiceUrl: nil, privacyPolicyUrl: nil, skipTermsOfServiceCollection: nil, collectionOptions: .init())
let savedOnboardingSettings = AppSettings.shared.onboardingSettings
viewControllerToPush = embeddedComponentManager.createAccountOnboardingViewController(
fullTermsOfServiceUrl: savedOnboardingSettings.fullTermsOfServiceUrl,
recipientTermsOfServiceUrl: savedOnboardingSettings.recipientTermsOfServiceUrl,
privacyPolicyUrl: savedOnboardingSettings.privacyPolicyUrl,
skipTermsOfServiceCollection: savedOnboardingSettings.skipTermsOfService.boolValue,
collectionOptions: savedOnboardingSettings.accountCollectionOptions)
case .payouts:
viewControllerToPush = embeddedComponentManager.createPayoutsViewController()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ struct AppSettingsView: View {

@State var selectedMerchant: MerchantInfo?
@State var serverURLString: String = AppSettings.shared.selectedServerBaseURL

@State var onboardingSettings = AppSettings.shared.onboardingSettings

var isCustomEndpointValid: Bool {
guard let url = URL(string: serverURLString) else { return false }
return UIApplication.shared.canOpenURL(url)
URL(string: serverURLString)?.isValid == true
}

var saveEnabled: Bool {
Expand Down Expand Up @@ -49,6 +49,17 @@ struct AppSettingsView: View {
} header: {
Text("Select a demo account")
}
Section {
NavigationLink {
OnboardingSettingsView(onboardingSettings: $onboardingSettings)
} label: {
Text("Onboarding")
.font(.body)
.foregroundColor(.primary)
}
} header: {
Text("Component Settings")
}
Section {
TextInput(label: "", placeholder: "https://example.com", text: $serverURLString, isValid: isCustomEndpointValid)
Button {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// OnboardingSettings.swift
// StripeConnect Example
//
// Created by Chris Mays on 9/20/24.
//

import Foundation
@_spi(PrivateBetaConnect) import StripeConnect

struct OnboardingSettings: Equatable {
var fullTermsOfServiceString: String = ""

var fullTermsOfServiceUrl: URL? {
!fullTermsOfServiceString.isEmpty ? URL(string: fullTermsOfServiceString) : nil
}

var recipientTermsOfServiceString: String = ""
var recipientTermsOfServiceUrl: URL? {
!recipientTermsOfServiceString.isEmpty ? URL(string: recipientTermsOfServiceString) : nil
}

var privacyPolicyString: String = ""
var privacyPolicyUrl: URL? {
!privacyPolicyString.isEmpty ? URL(string: privacyPolicyString) : nil
}

var skipTermsOfService: ToggleOption = .default
var fieldOption: FieldOption = .default
var futureRequirement: FutureRequirement = .default

var accountCollectionOptions: AccountCollectionOptions {
var accountCollectionOptions: AccountCollectionOptions = .init()
switch fieldOption {
case .default:
// Default set nothing
break
case .currentlyDue:
accountCollectionOptions.fields = .currentlyDue
case .eventuallyDue:
accountCollectionOptions.fields = .eventuallyDue
}

switch futureRequirement {
case .default:
// Default set nothing
break
case .omit:
accountCollectionOptions.futureRequirements = .omit
case .include:
accountCollectionOptions.futureRequirements = .include
}

return accountCollectionOptions
}

enum FutureRequirement: String, CaseIterable, Identifiable {
var id: String {
self.rawValue
}

case `default`
case omit
case include

var displayLabel: String {
switch self {
case .default:
return "Default"
case .omit:
return "Omit"
case .include:
return "Include"
}
}
}

enum FieldOption: String, CaseIterable, Identifiable {
var id: String {
self.rawValue
}

case `default`
case currentlyDue
case eventuallyDue

var displayLabel: String {
switch self {
case .default:
return "Default"
case .currentlyDue:
return "Currently due"
case .eventuallyDue:
return "Eventually due"
}
}
}

enum ToggleOption: String, CaseIterable, Identifiable {
var id: String {
self.rawValue
}

case `default`
case `false`
case `true`

var displayLabel: String {
switch self {
case .default:
return "Default"
case .false:
return "False"
case .true:
return "True"
}
}

var boolValue: Bool? {
switch self {
case .default:
return nil
case .false:
return false
case .true:
return true
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// OnboardingSettings.swift
// StripeConnect Example
//
// Created by Chris Mays on 9/20/24.
//

import SwiftUI

struct OnboardingSettingsView: View {

@Environment(\.dismiss) var dismiss

@Binding var onboardingSettings: OnboardingSettings

var saveEnabled: Bool {
AppSettings.shared.onboardingSettings != onboardingSettings
&& isValidURL(onboardingSettings.fullTermsOfServiceString)
&& isValidURL(onboardingSettings.recipientTermsOfServiceString)
&& isValidURL( onboardingSettings.privacyPolicyString)
}

var body: some View {
List {
Section {
TextInput(label: "Full terms of service", placeholder: "https://example.com", text: $onboardingSettings.fullTermsOfServiceString, isValid: isValidURL(onboardingSettings.fullTermsOfServiceString))
TextInput(label: "Recipient terms of service", placeholder: "https://example.com", text: $onboardingSettings.recipientTermsOfServiceString, isValid: isValidURL(onboardingSettings.recipientTermsOfServiceString))
TextInput(label: "Privacy policy", placeholder: "https://example.com", text: $onboardingSettings.privacyPolicyString, isValid: isValidURL( onboardingSettings.privacyPolicyString))

Picker("Skip terms of service", selection: $onboardingSettings.skipTermsOfService) {
ForEach(OnboardingSettings.ToggleOption.allCases) { option in
Text(option.displayLabel)
.tag(option)
}
}

Picker("Field option", selection: $onboardingSettings.fieldOption) {
ForEach(OnboardingSettings.FieldOption.allCases) { option in
Text(option.displayLabel)
.tag(option)
}
}

Picker("Future requirements", selection: $onboardingSettings.futureRequirement) {
ForEach(OnboardingSettings.FutureRequirement.allCases) { option in
Text(option.displayLabel)
.tag(option)
}
}

Button {
onboardingSettings = .init()
AppSettings.shared.onboardingSettings = onboardingSettings
} label: {
Text("Reset to defaults")
}
} header: {
}
.textInputAutocapitalization(.never)
}
.listStyle(.insetGrouped)
.navigationTitle("Onboarding Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
AppSettings.shared.onboardingSettings = onboardingSettings
dismiss()
} label: {
Text("Save")
}
.disabled(!saveEnabled)
}
}
.environment(\.horizontalSizeClass, .compact)
}

func isValidURL(_ url: String) -> Bool {
URL(string: url)?.isValid == true || url.isEmpty
}
}
Loading

0 comments on commit d94be4c

Please sign in to comment.