Skip to content
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

Invitation Codes #54

Merged
merged 45 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
3226bad
Helpers for invitation codes
MatthewTurk247 Mar 10, 2024
97c9e2c
Simplify `invitationCodeIsValid`
MatthewTurk247 Mar 10, 2024
b51a37b
Some documentation for storing codes
MatthewTurk247 Mar 11, 2024
dd54d73
Update user document with invitation code
MatthewTurk247 Mar 11, 2024
b74930c
First attempt at `beforecreated`
MatthewTurk247 Mar 16, 2024
7224490
Init functions folder
MatthewTurk247 Mar 16, 2024
b2a369a
Pointing to (emulator) cloud functions
MatthewTurk247 Mar 16, 2024
af08661
Fixed typo
MatthewTurk247 Mar 16, 2024
655ba46
Remove leftover lines
MatthewTurk247 Mar 16, 2024
ea86a58
Use emulator depending on flag
MatthewTurk247 Mar 17, 2024
e6a2e15
Fixed typo
MatthewTurk247 Mar 21, 2024
af20323
Merge branch 'main' into invitation-codes
PSchmiedmayer Mar 21, 2024
28f9948
Smaller things noticed during the review
PSchmiedmayer Mar 22, 2024
c192752
Check that user doc exists and contains invitation
MatthewTurk247 Mar 25, 2024
113fb7c
Update to reflect the headers of the PAWS project
MatthewTurk247 Mar 25, 2024
8a732e9
Invitation codes work in emulator
MatthewTurk247 Mar 27, 2024
9ad7326
Up and running with `beforecreated`
MatthewTurk247 Mar 27, 2024
eb3e0a9
Add license files
MatthewTurk247 Mar 27, 2024
840accb
Add copyright and licensing for `functions/*.json`
MatthewTurk247 Mar 27, 2024
7470cbd
Outline `AccountCreationTests`
MatthewTurk247 Mar 27, 2024
1f50398
Remove empty test case
MatthewTurk247 Mar 27, 2024
ba71beb
Update PAWS/Onboarding/InvitationCodeError.swift
MatthewTurk247 Mar 27, 2024
8260bd9
Minor revision to logic
MatthewTurk247 Mar 27, 2024
a801ca5
Fixed typo
MatthewTurk247 Mar 27, 2024
73df52a
Additional emulator setup
MatthewTurk247 Mar 28, 2024
0d683a6
Additional emulator setup
MatthewTurk247 Mar 29, 2024
fde6b56
Add MIT license info for `firebase` directory
MatthewTurk247 Mar 29, 2024
010cda3
UI test adaptations from SpeziTemplateApplication
MatthewTurk247 Mar 29, 2024
afa6c74
Minor refactoring
MatthewTurk247 Mar 29, 2024
2d24d83
Improved UI tests for date picker
MatthewTurk247 Mar 30, 2024
5353ea9
Refactor UI tests
MatthewTurk247 Apr 2, 2024
951349f
Merge branch 'main' of https://github.com/StanfordBDHG/PediatricApple…
MatthewTurk247 Apr 2, 2024
cfe3988
Upgrade to latest version of HealthKitOnFHIR
MatthewTurk247 Apr 3, 2024
953c8dd
Upgrade SpeziFirebase to latest version
MatthewTurk247 Apr 4, 2024
a6a3657
Refactor cloud functions
MatthewTurk247 Apr 4, 2024
5ec516e
Secondary button for `InvitationCodeView`
MatthewTurk247 Apr 4, 2024
de1bd78
`testOnboardingFlow` is almost there
MatthewTurk247 Apr 5, 2024
30ca5e6
Minor refactoring
MatthewTurk247 Apr 5, 2024
61e17c0
Remove leftover code
MatthewTurk247 Apr 5, 2024
d7b439a
Adjust strings and indentation
MatthewTurk247 Apr 7, 2024
d8161a0
Update InvitationCodeView.swift
MatthewTurk247 Apr 7, 2024
0001e56
Update InvitationCodeView.swift
MatthewTurk247 Apr 7, 2024
2cdf86c
License headers for package-lock.json and package.json
MatthewTurk247 Apr 7, 2024
b7a39b1
Remove leftover lines
MatthewTurk247 Apr 7, 2024
91d3be6
Rerun tests
MatthewTurk247 Apr 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .reuse/dep5
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ Copyright: 2024 Stanford University and the project authors (see CONTRIBUTORS.md
License: MIT
Comment: All files are part of the Stanford Spezi Data Pipeline Template open source project.

Files: functions/*.json
Copyright: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
License: MIT
Comment: All files are part of the PAWS application based on the Stanford Spezi Template Application project.
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved

20 changes: 20 additions & 0 deletions PAWS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
A9DFE8A92ABE551400428242 /* AccountButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DFE8A82ABE551400428242 /* AccountButton.swift */; };
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */; };
B28A0DDC2BA4AEDE0068258D /* Date+Bool.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28A0DDB2BA4AEDE0068258D /* Date+Bool.swift */; };
B2DB1AD32BB4BCB100B0F49B /* AccountCreationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */; };
B2F7F1DC2BA53F6400BE93BE /* InvitationCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */; };
B2F7F1DF2BA540BB00BE93BE /* FirebaseFunctions in Frameworks */ = {isa = PBXBuildFile; productRef = B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */; };
B2F7F1E22BA549A900BE93BE /* InvitationCodeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -145,6 +149,9 @@
A9DFE8A82ABE551400428242 /* AccountButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountButton.swift; sourceTree = "<group>"; };
A9FE7ACF2AA39BAB0077B045 /* AccountSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSheet.swift; sourceTree = "<group>"; };
B28A0DDB2BA4AEDE0068258D /* Date+Bool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Bool.swift"; sourceTree = "<group>"; };
B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCreationTests.swift; sourceTree = "<group>"; };
B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCodeView.swift; sourceTree = "<group>"; };
B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCodeError.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -169,6 +176,7 @@
2FF53D8B2A8725DE00042B76 /* SpeziMockWebService in Frameworks */,
2FE5DC7229EDD8D3004B9AB4 /* SpeziHealthKit in Frameworks */,
2F49B7762980407C00BCB272 /* Spezi in Frameworks */,
B2F7F1DF2BA540BB00BE93BE /* FirebaseFunctions in Frameworks */,
2FE5DC8F29EDD980004B9AB4 /* SpeziViews in Frameworks */,
2F3D4ABC2A4E7C290068FB2F /* SpeziScheduler in Frameworks */,
2FBD738C2A3BD150004228E7 /* SpeziScheduler in Frameworks */,
Expand Down Expand Up @@ -239,6 +247,8 @@
2FE5DC3129EDD7CA004B9AB4 /* OnboardingFlow.swift */,
2FE5DC3429EDD7CA004B9AB4 /* Welcome.swift */,
2FE5DC3229EDD7CA004B9AB4 /* InterestingModules.swift */,
B2F7F1DB2BA53F6400BE93BE /* InvitationCodeView.swift */,
B2F7F1E12BA549A900BE93BE /* InvitationCodeError.swift */,
2FE5DCAC29EE6107004B9AB4 /* AccountOnboarding.swift */,
2FE5DC2F29EDD7CA004B9AB4 /* Consent.swift */,
2FE5DC3029EDD7CA004B9AB4 /* HealthKitPermissions.swift */,
Expand Down Expand Up @@ -367,6 +377,7 @@
isa = PBXGroup;
children = (
2F4E237D2989A2FE0013F3D9 /* LaunchTests.swift */,
B2DB1AD22BB4BCB100B0F49B /* AccountCreationTests.swift */,
);
path = PAWSUITests;
sourceTree = "<group>";
Expand Down Expand Up @@ -429,6 +440,7 @@
9739A0C52AD7B5730084BEA5 /* FirebaseStorage */,
97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */,
A9D83F952B083794000D0C78 /* SpeziFirebaseAccountStorage */,
B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */,
);
productName = PAWS;
productReference = 653A254D283387FE005D4D48 /* PAWS.app */;
Expand Down Expand Up @@ -587,6 +599,7 @@
2FE5DC4729EDD7F2004B9AB4 /* CodableArray+RawRepresentable.swift in Sources */,
A9720E432ABB68CC00872D23 /* AccountSetupHeader.swift in Sources */,
2FE5DC4029EDD7EE004B9AB4 /* FeatureFlags.swift in Sources */,
B2F7F1DC2BA53F6400BE93BE /* InvitationCodeView.swift in Sources */,
2FE5DC4629EDD7F2004B9AB4 /* Bundle+Image.swift in Sources */,
2FFD22FC2B59AED8005DD268 /* FAQ.swift in Sources */,
2F4E23832989D51F0013F3D9 /* PAWSTestingSetup.swift in Sources */,
Expand All @@ -601,6 +614,7 @@
A9FE7AD02AA39BAB0077B045 /* AccountSheet.swift in Sources */,
653A2551283387FE005D4D48 /* PAWS.swift in Sources */,
2FFD22F62B59ABE2005DD268 /* PAWSCard.swift in Sources */,
B2F7F1E22BA549A900BE93BE /* InvitationCodeError.swift in Sources */,
2FE5DC3629EDD7CA004B9AB4 /* HealthKitPermissions.swift in Sources */,
2F65B44E2A3B8B0600A36932 /* NotificationPermissions.swift in Sources */,
5661552E2AB854C000209B80 /* PackageHelper.swift in Sources */,
Expand All @@ -621,6 +635,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B2DB1AD32BB4BCB100B0F49B /* AccountCreationTests.swift in Sources */,
2F4E237E2989A2FE0013F3D9 /* LaunchTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -1396,6 +1411,11 @@
package = 2FE5DC7329EDD8E6004B9AB4 /* XCRemoteSwiftPackageReference "SpeziFirebase" */;
productName = SpeziFirebaseAccountStorage;
};
B2F7F1DE2BA540BB00BE93BE /* FirebaseFunctions */ = {
isa = XCSwiftPackageProductDependency;
package = 2FE5DC9029EDD9C3004B9AB4 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseFunctions;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 653A2545283387FE005D4D48 /* Project object */;
Expand Down
2 changes: 1 addition & 1 deletion PAWS/ECGRecordings/ECGRecording.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct ECGRecording: View {
var body: some View {
PAWSCard {
VStack(alignment: .leading) {
Text("EEG Recording")
Text("ECG Recording")
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
.font(.title)
Text(electrocardiogram.endDate.formatted())
.font(.subheadline)
Expand Down
27 changes: 27 additions & 0 deletions PAWS/Onboarding/InvitationCodeError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// This source file is part of the PAWS application based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation


enum InvitationCodeError: LocalizedError {
case invitationCodeInvalid
case userNotAuthenticated
case generalError(String)

var errorDescription: String? {
switch self {
case .invitationCodeInvalid:
NSLocalizedString("The invitation code is invalid or has already been used.", comment: "Invitation Code Invalid")
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
case .userNotAuthenticated:
NSLocalizedString("User authentication failed. Please try to sign in again.", comment: "User Not Authenticated")
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
case .generalError(let message):
String(format: NSLocalizedString("An error occurred: %@", comment: "General Error"), message)
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
153 changes: 153 additions & 0 deletions PAWS/Onboarding/InvitationCodeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//
// This source file is part of the PAWS application based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Firebase
import FirebaseAuth
import FirebaseFunctions
import SpeziOnboarding
import SpeziValidation
import SpeziViews
import SwiftUI


struct InvitationCodeView: View {
@Environment(OnboardingNavigationPath.self) private var onboardingNavigationPath
@State private var invitationCode = ""
@State private var viewState: ViewState = .idle
@ValidationState private var validation


var body: some View {
ScrollView {
VStack(spacing: 32) {
invitationCodeHeader
Divider()
Grid(horizontalSpacing: 16, verticalSpacing: 16) {
invitationCodeView
}
.padding(.top, -8)
.padding(.bottom, -12)
Divider()
OnboardingActionsView(
"Redeem Invitation Code",
action: {
guard validation.validateSubviews() else {
return
}

await verifyOnboardingCode()
}
)
.disabled(invitationCode.isEmpty)
}
.padding(.horizontal)
.padding(.bottom)
.viewStateAlert(state: $viewState)
.navigationBarTitleDisplayMode(.large)
.navigationTitle(String(localized: "Invitation Code"))
}
}


@ViewBuilder private var invitationCodeView: some View {
DescriptionGridRow {
Text("Invitation Code")
} content: {
VerifiableTextField(
LocalizedStringResource("Invitation Code"),
text: $invitationCode
)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.characters)
.textContentType(.oneTimeCode)
.validate(input: invitationCode, rules: [invitationCodeValidationRule])
}
.receiveValidation(in: $validation)
}

@ViewBuilder private var invitationCodeHeader: some View {
VStack(spacing: 32) {
Image(systemName: "rectangle.and.pencil.and.ellipsis")
.resizable()
.scaledToFit()
.frame(height: 100)
.accessibilityHidden(true)
.foregroundStyle(Color.accentColor)
Text("Plase enter your invitation code to join the PAWS study.")
}
}

private var invitationCodeValidationRule: ValidationRule {
ValidationRule(
rule: { invitationCode in
invitationCode.count >= 8
},
message: "An invitation code is at least 8 characters long."
)
}

init() {
if FeatureFlags.useFirebaseEmulator {
Functions.functions().useEmulator(withHost: "localhost", port: 5001)
}
}

private func verifyOnboardingCode() async {
do {
if FeatureFlags.disableFirebase {
guard invitationCode == "VASCTRAC" else {
throw InvitationCodeError.invitationCodeInvalid
}

try? await Task.sleep(for: .seconds(0.25))
} else {
if Auth.auth().currentUser == nil {
try await Auth.auth().signInAnonymously()
}

let checkInvitationCode = Functions.functions().httpsCallable("checkInvitationCode")

do {
_ = try await checkInvitationCode.call(
[
"invitationCode": invitationCode
]
)
} catch {
throw InvitationCodeError.invitationCodeInvalid
}
}

await onboardingNavigationPath.nextStep()
} catch let error as NSError {
if let errorCode = FunctionsErrorCode(rawValue: error.code) {
// Handle Firebase-specific errors.
switch errorCode {
case .unauthenticated:
viewState = .error(InvitationCodeError.userNotAuthenticated)
case .notFound:
viewState = .error(InvitationCodeError.invitationCodeInvalid)
default:
viewState = .error(InvitationCodeError.generalError(error.localizedDescription))
}
} else {
// Handle other errors, such as network issues or unexpected behavior.
viewState = .error(InvitationCodeError.generalError(error.localizedDescription))
}
}
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
}
}


#Preview {
FirebaseApp.configure()

return OnboardingStack {
InvitationCodeView()
}
}
MatthewTurk247 marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions PAWS/Onboarding/OnboardingFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct OnboardingFlow: View {
InterestingModules()

if !FeatureFlags.disableFirebase {
InvitationCodeView()
AccountOnboarding()
}

Expand Down
25 changes: 23 additions & 2 deletions PAWS/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
}
}
}
},
"An error occurred: %@" : {
"comment" : "General Error"
},
"An invitation code is at least 8 characters long." : {

},
"ANSWER_1" : {
"extractionState" : "manual",
Expand Down Expand Up @@ -223,10 +229,10 @@
}
}
},
"ECG Recordings" : {
"ECG Recording" : {

},
"EEG Recording" : {
"ECG Recordings" : {

},
"FAQ" : {
Expand Down Expand Up @@ -374,6 +380,9 @@
}
}
}
},
"Invitation Code" : {

},
"LICENSE_INFO_TITLE" : {
"localizations" : {
Expand Down Expand Up @@ -423,6 +432,9 @@
}
}
}
},
"Plase enter your invitation code to join the PAWS study." : {

},
"PROJECT_LICENSE_DESCRIPTION" : {
"localizations" : {
Expand Down Expand Up @@ -538,6 +550,9 @@
},
"Recorded no symptoms" : {

},
"Redeem Invitation Code" : {

},
"Repository Link" : {
"localizations" : {
Expand Down Expand Up @@ -584,6 +599,12 @@
},
"Thank you for participating in the PAWS study!" : {

},
"The invitation code is invalid or has already been used." : {
"comment" : "Invitation Code Invalid"
},
"User authentication failed. Please try to sign in again." : {
"comment" : "User Not Authenticated"
},
"WELCOME_AREA1_DESCRIPTION" : {
"localizations" : {
Expand Down
Loading
Loading