From f65cd49ebea21c82069b90c61ca401aceb753611 Mon Sep 17 00:00:00 2001 From: Yashwanth Ravipati Date: Mon, 15 May 2023 03:49:25 -0700 Subject: [PATCH 01/33] Status and Profile Routing --- ALUM/ALUM/Models/CurrentUserModel.swift | 20 ++++- .../FirebaseAuthenticationService.swift | 2 +- ALUM/ALUM/Views/LoggedInRouter.swift | 85 +++++++++++-------- ALUM/ALUM/Views/MenteeProfileScreen.swift | 14 +-- ALUM/ALUM/Views/MentorProfileScreen.swift | 6 +- 5 files changed, 81 insertions(+), 46 deletions(-) diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index a5ed9ffc..32b553e1 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -20,23 +20,26 @@ class CurrentUserModel: ObservableObject { @Published var uid: String? @Published var role: UserRole? @Published var isLoggedIn: Bool + @Published var status: String? init() { self.isLoading = true self.isLoggedIn = false self.uid = nil self.role = nil + self.status = nil } /// Since async operations are involved, this function will limit updating the current /// user without using the DispatchQueue logic. /// Not using DispatchQueue can casue race conditions which can crash our app - func setCurrentUser(isLoading: Bool, isLoggedIn: Bool, uid: String?, role: UserRole?) { + func setCurrentUser(isLoading: Bool, isLoggedIn: Bool, uid: String?, role: UserRole?, status: String?) { DispatchQueue.main.async { self.isLoading = isLoading self.isLoggedIn = isLoggedIn self.uid = uid self.role = role + self.status = status } } @@ -48,7 +51,7 @@ class CurrentUserModel: ObservableObject { } try await self.setFromFirebaseUser(user: user) } catch { - self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) + self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil, status: nil) } } @@ -74,6 +77,17 @@ class CurrentUserModel: ObservableObject { message: "Expected user role to be mentor OR mentee but found - \(role)" ) } - self.setCurrentUser(isLoading: false, isLoggedIn: true, uid: user.uid, role: roleEnum) + await self.setCurrentUser(isLoading: false, isLoggedIn: true, uid: user.uid, role: roleEnum, + status: try getStatus(userID: user.uid, roleEnum: roleEnum)) + } + func getStatus(userID: String, roleEnum: UserRole) async throws -> String { + let userStatus: String + switch roleEnum { + case .mentee: + userStatus = try await UserService.shared.getMentee(userID: userID).mentee.status ?? "" + case .mentor: + userStatus = try await UserService.shared.getMentor(userID: userID).mentor.status ?? "" + } + return userStatus } } diff --git a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift index 533ff89d..6a32ced0 100644 --- a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift +++ b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift @@ -21,7 +21,7 @@ final class FirebaseAuthenticationService: ObservableObject { func logout() { do { try Auth.auth().signOut() - self.currentUser.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) + self.currentUser.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil, status: nil) print("logged out successfuly") } catch let error { print("error occured in FirebaseAuthenticationService - ", error.localizedDescription) diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/LoggedInRouter.swift index 50214fac..7d9a94f9 100644 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/LoggedInRouter.swift @@ -40,6 +40,7 @@ struct PlaceHolderHomeScreen: View { } struct LoggedInRouter: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared // (todo) Needs to be customized to match our design @State private var selection = 0 init() { @@ -52,48 +53,62 @@ struct LoggedInRouter: View { ] var body: some View { - VStack(spacing: 0) { - switch selection { - case 0: - MentorSessionDetailsPage() - case 1: - ProfileRouter() - default: - Text("Error") + if currentUser.status == "under review" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentee."]) + } + else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentor."]) } - ZStack(alignment: .bottom) { - HStack(spacing: 0) { - ForEach(0.. Date: Tue, 16 May 2023 14:51:55 -0700 Subject: [PATCH 02/33] Adding Approved Status and Adding Navigation to MentorProfileScreen --- ALUM/ALUM/Models/CurrentUserModel.swift | 2 + ALUM/ALUM/Views/LoggedInRouter.swift | 80 +++++++++++++++-------- ALUM/ALUM/Views/MenteeProfileScreen.swift | 13 +++- ALUM/ALUM/Views/MentorProfileScreen.swift | 22 +++++-- 4 files changed, 80 insertions(+), 37 deletions(-) diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 32b553e1..cb62b957 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -21,6 +21,7 @@ class CurrentUserModel: ObservableObject { @Published var role: UserRole? @Published var isLoggedIn: Bool @Published var status: String? + @Published var showTabBar: Bool init() { self.isLoading = true @@ -28,6 +29,7 @@ class CurrentUserModel: ObservableObject { self.uid = nil self.role = nil self.status = nil + self.showTabBar = true } /// Since async operations are involved, this function will limit updating the current diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/LoggedInRouter.swift index 7d9a94f9..a5c0a049 100644 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/LoggedInRouter.swift @@ -16,8 +16,20 @@ struct ProfileRouter: View { switch self.currentUser.role { case .some(UserRole.mentor): MentorProfileScreen(uID: self.currentUser.uid!) + .onDisappear(perform: { + self.currentUser.showTabBar = false + }) + .onAppear(perform: { + self.currentUser.showTabBar = true + }) case .some(UserRole.mentee): MenteeProfileScreen(uID: self.currentUser.uid!) + .onDisappear(perform: { + self.currentUser.showTabBar = false + }) + .onAppear(perform: { + self.currentUser.showTabBar = true + }) case .none: Text("Internal Error: User Role is nil") } @@ -58,14 +70,22 @@ struct LoggedInRouter: View { LoginReviewPage(text: ["Application is under review", "It usually takes 3-5 days to process your application as a mentee."]) - } - else if currentUser.role == .mentor { + } else if currentUser.role == .mentor { LoginReviewPage(text: ["Application is under review", "It usually takes 3-5 days to process your application as a mentor."]) } - } - else { + } else if currentUser.status == "approved" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Matching you with a mentor", + "We are looking for a perfect mentor for you. Please allow us some time!"]) + } else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Matching you with a mentee", + "We are looking for a perfect mentee for you. Please allow us some time!"]) + } + } else { VStack(spacing: 0) { switch selection { case 0: @@ -75,36 +95,38 @@ struct LoggedInRouter: View { default: Text("Error") } - ZStack(alignment: .bottom) { - HStack(spacing: 0) { - ForEach(0.. Date: Thu, 18 May 2023 14:42:01 -0700 Subject: [PATCH 03/33] CustomNavBar --- ALUM/ALUM.xcodeproj/project.pbxproj | 28 +++++++++ .../ALUM Beige.colorset/Contents.json | 20 +++++++ .../ALUM White.colorset/Contents.json | 6 +- .../CustomNavBarContainerView.swift | 48 +++++++++++++++ .../CustomNavBarPreferenceKeys.swift | 57 ++++++++++++++++++ .../CustomNavBar/CustomNavBarView.swift | 60 +++++++++++++++++++ .../CustomNavBar/CustomNavLink.swift | 39 ++++++++++++ .../CustomNavBar/CustomNavView.swift | 34 +++++++++++ .../ALUM/Components/NavigationComponent.swift | 1 - ALUM/ALUM/Views/LoggedInRouter.swift | 6 +- 10 files changed, 294 insertions(+), 5 deletions(-) create mode 100644 ALUM/ALUM/Assets.xcassets/ALUM Beige.colorset/Contents.json create mode 100644 ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift create mode 100644 ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift create mode 100644 ALUM/ALUM/Components/CustomNavBar/CustomNavBarView.swift create mode 100644 ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift create mode 100644 ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index e244b7e2..697a3a6f 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -30,6 +30,11 @@ 0757553829FAA350008E73FB /* QuestionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553729FAA350008E73FB /* QuestionModel.swift */; }; 0757553A29FAA380008E73FB /* QuestionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553929FAA380008E73FB /* QuestionViewModel.swift */; }; 0757553C29FAA387008E73FB /* NotesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553B29FAA387008E73FB /* NotesService.swift */; }; + 0793B41E2A19FA7200AF78C8 /* CustomNavBarContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0793B4192A19FA7200AF78C8 /* CustomNavBarContainerView.swift */; }; + 0793B41F2A19FA7200AF78C8 /* CustomNavView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0793B41A2A19FA7200AF78C8 /* CustomNavView.swift */; }; + 0793B4202A19FA7200AF78C8 /* CustomNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0793B41B2A19FA7200AF78C8 /* CustomNavLink.swift */; }; + 0793B4212A19FA7200AF78C8 /* CustomNavBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0793B41C2A19FA7200AF78C8 /* CustomNavBarView.swift */; }; + 0793B4222A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0793B41D2A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift */; }; 0794653429DC536200B68EFD /* InputValidationComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9756DF76298233B500A0BCB5 /* InputValidationComponent.swift */; }; 0799ACE32A007E8A00EEAFA2 /* LoadingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */; }; 0799ACE52A00924F00EEAFA2 /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */; }; @@ -143,6 +148,11 @@ 0757553729FAA350008E73FB /* QuestionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuestionModel.swift; sourceTree = ""; }; 0757553929FAA380008E73FB /* QuestionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuestionViewModel.swift; sourceTree = ""; }; 0757553B29FAA387008E73FB /* NotesService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesService.swift; sourceTree = ""; }; + 0793B4192A19FA7200AF78C8 /* CustomNavBarContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavBarContainerView.swift; sourceTree = ""; }; + 0793B41A2A19FA7200AF78C8 /* CustomNavView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavView.swift; sourceTree = ""; }; + 0793B41B2A19FA7200AF78C8 /* CustomNavLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavLink.swift; sourceTree = ""; }; + 0793B41C2A19FA7200AF78C8 /* CustomNavBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavBarView.swift; sourceTree = ""; }; + 0793B41D2A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavBarPreferenceKeys.swift; sourceTree = ""; }; 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreen.swift; sourceTree = ""; }; 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInRouter.swift; sourceTree = ""; }; 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; @@ -302,6 +312,18 @@ path = "Preview Content"; sourceTree = ""; }; + 0793B4182A19FA7200AF78C8 /* CustomNavBar */ = { + isa = PBXGroup; + children = ( + 0793B4192A19FA7200AF78C8 /* CustomNavBarContainerView.swift */, + 0793B41A2A19FA7200AF78C8 /* CustomNavView.swift */, + 0793B41B2A19FA7200AF78C8 /* CustomNavLink.swift */, + 0793B41C2A19FA7200AF78C8 /* CustomNavBarView.swift */, + 0793B41D2A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift */, + ); + path = CustomNavBar; + sourceTree = ""; + }; 0799ACEE2A0102E400EEAFA2 /* Recovered References */ = { isa = PBXGroup; children = ( @@ -312,6 +334,7 @@ 07F9A50C297180D700BC11A8 /* Components */ = { isa = PBXGroup; children = ( + 0793B4182A19FA7200AF78C8 /* CustomNavBar */, 0757551B29FAA2AB008E73FB /* Bullet.swift */, 0757551C29FAA2AB008E73FB /* CircleAddButton.swift */, 0757551D29FAA2AB008E73FB /* CustomAlertView.swift */, @@ -634,6 +657,7 @@ 80210ECA29BA82E7008B912A /* MenteeProfileViewModel.swift in Sources */, 8041161E29B21BB800529936 /* MentorProfileScreen.swift in Sources */, 8095FE7029E20C89006AA63C /* WhyPairedComponent.swift in Sources */, + 0793B41E2A19FA7200AF78C8 /* CustomNavBarContainerView.swift in Sources */, E365F71E29FA0D110055A2F4 /* SessionService.swift in Sources */, 97E439F229AD97B100F0B7C1 /* SignUpJoinAsScreen.swift in Sources */, 979303D629E872190053C30E /* FormIncompleteComponent.swift in Sources */, @@ -673,6 +697,8 @@ 0757553829FAA350008E73FB /* QuestionModel.swift in Sources */, 0757553429FAA2D6008E73FB /* SessionConfirmationScreen.swift in Sources */, 9760067C299FFCD2000945E2 /* SignUpPageView.swift in Sources */, + 0793B41F2A19FA7200AF78C8 /* CustomNavView.swift in Sources */, + 0793B4202A19FA7200AF78C8 /* CustomNavLink.swift in Sources */, 97F6CDF629BD4E3D00DFBB99 /* CareerInterests.swift in Sources */, 741D534229A4EDD3004C04E6 /* TagDisplay.swift in Sources */, 9752A4EA2978B90F001E0AAB /* TextInputFieldComponent.swift in Sources */, @@ -692,12 +718,14 @@ 0757553A29FAA380008E73FB /* QuestionViewModel.swift in Sources */, 80210EC129B95F24008B912A /* MenteeCard.swift in Sources */, 80210ECC29BA9EBC008B912A /* MentorCard.swift in Sources */, + 0793B4222A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift in Sources */, 80210EC529BA8255008B912A /* MenteeProfileScreen.swift in Sources */, 0748208F29712921004AF547 /* ALUMApp.swift in Sources */, 80210EBD29B95D50008B912A /* ProfileModel.swift in Sources */, 979303DC29E885140053C30E /* MenteeSessionsDetailsPage.swift in Sources */, 97A2FE8C2989C20900405FD6 /* LoginScreen.swift in Sources */, 80210ED429C3DBD9008B912A /* FirebaseAuthenticationService.swift in Sources */, + 0793B4212A19FA7200AF78C8 /* CustomNavBarView.swift in Sources */, 80F5388C29B0252F00FB5E66 /* UserService.swift in Sources */, 80274FF5299DB03900CCB9D0 /* LoginViewModel.swift in Sources */, ); diff --git a/ALUM/ALUM/Assets.xcassets/ALUM Beige.colorset/Contents.json b/ALUM/ALUM/Assets.xcassets/ALUM Beige.colorset/Contents.json new file mode 100644 index 00000000..9e2083c0 --- /dev/null +++ b/ALUM/ALUM/Assets.xcassets/ALUM Beige.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF6", + "green" : "0xFA", + "red" : "0xFC" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ALUM/ALUM/Assets.xcassets/ALUM White.colorset/Contents.json b/ALUM/ALUM/Assets.xcassets/ALUM White.colorset/Contents.json index ae7b13d5..d69a7a91 100644 --- a/ALUM/ALUM/Assets.xcassets/ALUM White.colorset/Contents.json +++ b/ALUM/ALUM/Assets.xcassets/ALUM White.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xF6", - "green" : "0xFA", - "red" : "0xFC" + "blue" : "0.965", + "green" : "0.980", + "red" : "0.988" } }, "idiom" : "universal" diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift new file mode 100644 index 00000000..97d76055 --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -0,0 +1,48 @@ +// +// CustomNavBarContainerView.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import SwiftUI + +struct CustomNavBarContainerView: View { + let content: Content + @State private var showBackButton: Bool = true + @State private var title: String = "Title" + @State private var isPurple: Bool = true + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + var body: some View { + VStack(spacing: 0) { + CustomNavBarView(showBackButton: showBackButton, title: title, isPurple: isPurple) + content + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .onPreferenceChange(CustomNavBarTitlePreferenceKey.self, perform: { + value in + self.title = value + }) + .onPreferenceChange(CustomNavBarIsPurplePreferenceKey.self, perform: { + value in + self.isPurple = value + }) + .onPreferenceChange(CustomNavBarBackButtonHiddenPreferenceKey.self, perform: { + value in + self.showBackButton = !value + }) + } +} + +struct CustomNavBarContainerView_Previews: PreviewProvider { + static var previews: some View { + CustomNavBarContainerView { + LoginReviewPage(text: ["Hello", "Hi"]) + .customNavigationTitle("Title 2") + .customNavigationIsPurple(true) + .customNavigationBarBackButtonHidden(false) + } + } +} diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift new file mode 100644 index 00000000..5846c82b --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -0,0 +1,57 @@ +// +// CustomNavBarPreferenceKeys.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import Foundation +import SwiftUI +//@State private var showBackButton: Bool = true +//@State private var title: String = "Title" +//@State private var isPurple: Bool = true + +struct CustomNavBarTitlePreferenceKey: PreferenceKey { + static var defaultValue: String = "" + + static func reduce(value: inout String, nextValue: () -> String) { + value = nextValue() + } +} + +struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { + static var defaultValue: Bool = true + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + +struct CustomNavBarBackButtonHiddenPreferenceKey: PreferenceKey { + static var defaultValue: Bool = false + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + +extension View { + func customNavigationTitle(_ title: String) -> some View { + preference(key: CustomNavBarTitlePreferenceKey.self, value: title) + } + + func customNavigationIsPurple(_ isPurple: Bool) -> some View { + preference(key: CustomNavBarIsPurplePreferenceKey.self, value: isPurple) + } + + func customNavigationBarBackButtonHidden(_ hidden: Bool) -> some View { + preference(key: CustomNavBarBackButtonHiddenPreferenceKey.self, value: hidden) + } + + func customNavBarItems(title: String = "", isPurple: Bool = true, backButtonHidden: Bool = false) -> some View { + self + .customNavigationTitle(title) + .customNavigationIsPurple(isPurple) + .customNavigationBarBackButtonHidden(backButtonHidden) + } +} diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarView.swift new file mode 100644 index 00000000..3f7f404c --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarView.swift @@ -0,0 +1,60 @@ +// +// CustomNavBar.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import SwiftUI + +struct CustomNavBarView: View { + @Environment(\.presentationMode) var presentationMode + let showBackButton: Bool + let title: String + let isPurple: Bool + var body: some View { + HStack { + if showBackButton { + backButton + } + Spacer() + titleSection + Spacer() + if showBackButton { + Image(systemName: "chevron.left") + .frame(width: 6, height: 12) + .opacity(0) + } + } + .padding() + .font(.headline) + .foregroundColor(isPurple ? Color("ALUM Beige") : Color("ALUM Primary Purple")) + .background(isPurple ? + Color("ALUM Primary Purple") : .white) + } +} + +struct CustomNavBar_Previews: PreviewProvider { + static var previews: some View { + VStack { + CustomNavBarView(showBackButton: true, title: "Title Here", isPurple: true) + Spacer() + } + } +} + +extension CustomNavBarView { + private var backButton: some View { + Button(action: { + presentationMode.wrappedValue.dismiss() + }, label: { + Image(systemName: "chevron.left") + .frame(width: 6, height: 12) + }) + } + private var titleSection: some View { + Text(title) + .font(.custom("Metropolis-Regular", size: 17)) + .foregroundColor(isPurple ? Color("ALUM Beige") : .black) + } +} diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift new file mode 100644 index 00000000..8a18740b --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -0,0 +1,39 @@ +// +// CustomNavLink.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import SwiftUI + +struct CustomNavLink: View { + let destination: Destination + let label: Label + init(destination: Destination, @ViewBuilder label: () -> Label) { + self.destination = destination + self.label = label() + } + var body: some View { + NavigationLink( + destination: CustomNavBarContainerView(content: { + destination + }) + .navigationBarHidden(true) + , + label: { label + }) + } +} + +struct CustomNavLink_Previews: PreviewProvider { + static var previews: some View { + CustomNavView { + CustomNavLink( + destination: Text("Destination")) { + Text("Click Me") + } + .customNavigationTitle("Title 2") + } + } +} diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift new file mode 100644 index 00000000..49d957e8 --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift @@ -0,0 +1,34 @@ +// +// CustomNavView.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import SwiftUI + +struct CustomNavView: View { + let content: Content + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + var body: some View { + NavigationView { + CustomNavBarContainerView { + content + } + .navigationBarHidden(true) + } + .navigationViewStyle(StackNavigationViewStyle()) + } +} + +struct CustomNavView_Previews: PreviewProvider { + static var previews: some View { + CustomNavView{ + LoginReviewPage(text: ["Hello", "Hi"]) + .customNavigationTitle("Title 2") + .customNavigationBarBackButtonHidden(false) + } + } +} diff --git a/ALUM/ALUM/Components/NavigationComponent.swift b/ALUM/ALUM/Components/NavigationComponent.swift index 2a374f2e..3fd73c3a 100644 --- a/ALUM/ALUM/Components/NavigationComponent.swift +++ b/ALUM/ALUM/Components/NavigationComponent.swift @@ -28,7 +28,6 @@ struct NavigationHeaderComponent: View { var body: some View { let foreColor = purple ? Color("ALUM White") : Color("ALUM Primary Purple") let titleColor = purple ? Color("ALUM White") : Color.black - ZStack { if showButton { Group { diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/LoggedInRouter.swift index a5c0a049..4cc1ab4b 100644 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/LoggedInRouter.swift @@ -91,7 +91,11 @@ struct LoggedInRouter: View { case 0: PlaceHolderHomeScreen() case 1: - ProfileRouter() + CustomNavView { + ProfileRouter() + .customNavigationTitle("Title") + .customNavigationBarBackButtonHidden(true) + } default: Text("Error") } From c6e691d1a3b89afa57ba4f173c7c9e569fc64c6b Mon Sep 17 00:00:00 2001 From: Yashwanth Ravipati Date: Thu, 18 May 2023 22:29:05 -0700 Subject: [PATCH 04/33] Using new navigation and improving mentor screens --- .../CustomNavBarContainerView.swift | 7 +- .../CustomNavBar/CustomNavView.swift | 4 +- ALUM/ALUM/Views/LoggedInRouter.swift | 25 +- ALUM/ALUM/Views/MenteeProfileScreen.swift | 189 ++++++++------- ALUM/ALUM/Views/MentorProfileScreen.swift | 229 ++++++++++-------- 5 files changed, 232 insertions(+), 222 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index 97d76055..2ff939f7 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -39,10 +39,9 @@ struct CustomNavBarContainerView: View { struct CustomNavBarContainerView_Previews: PreviewProvider { static var previews: some View { CustomNavBarContainerView { - LoginReviewPage(text: ["Hello", "Hi"]) - .customNavigationTitle("Title 2") - .customNavigationIsPurple(true) - .customNavigationBarBackButtonHidden(false) + VStack(spacing: 0) { + LoginReviewPage(text: ["Hello", "Hi"]) + } } } } diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift index 49d957e8..831d14fa 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift @@ -25,8 +25,8 @@ struct CustomNavView: View { struct CustomNavView_Previews: PreviewProvider { static var previews: some View { - CustomNavView{ - LoginReviewPage(text: ["Hello", "Hi"]) + CustomNavView { + MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") .customNavigationTitle("Title 2") .customNavigationBarBackButtonHidden(false) } diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/LoggedInRouter.swift index 4cc1ab4b..2392466b 100644 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/LoggedInRouter.swift @@ -16,20 +16,8 @@ struct ProfileRouter: View { switch self.currentUser.role { case .some(UserRole.mentor): MentorProfileScreen(uID: self.currentUser.uid!) - .onDisappear(perform: { - self.currentUser.showTabBar = false - }) - .onAppear(perform: { - self.currentUser.showTabBar = true - }) case .some(UserRole.mentee): MenteeProfileScreen(uID: self.currentUser.uid!) - .onDisappear(perform: { - self.currentUser.showTabBar = false - }) - .onAppear(perform: { - self.currentUser.showTabBar = true - }) case .none: Text("Internal Error: User Role is nil") } @@ -90,12 +78,14 @@ struct LoggedInRouter: View { switch selection { case 0: PlaceHolderHomeScreen() + .onAppear(perform: { + currentUser.showTabBar = true + }) case 1: - CustomNavView { - ProfileRouter() - .customNavigationTitle("Title") - .customNavigationBarBackButtonHidden(true) - } + ProfileRouter() + .onAppear(perform: { + currentUser.showTabBar = true + }) default: Text("Error") } @@ -132,7 +122,6 @@ struct LoggedInRouter: View { .frame(height: 45) } } - } } } diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index 65e94b51..50686920 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -12,7 +12,6 @@ struct MenteeProfileScreen: View { @StateObject private var viewModel = MenteeProfileViewmodel() @State var scrollAtTop: Bool = true @State var uID: String = "" - @State var prevView: AnyView = AnyView(LoadingView(text: "")) @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared var body: some View { @@ -21,7 +20,9 @@ struct MenteeProfileScreen: View { LoadingView(text: "MenteeProfileScreen") } else { content + .customNavigationIsPurple(scrollAtTop) .navigationBarBackButtonHidden(true) + .padding(.top, 0) } }.onAppear(perform: { Task { @@ -46,80 +47,14 @@ struct MenteeProfileScreen: View { .frame(width: 0, height: 0) .foregroundColor(Color("ALUM Primary Purple")) .onChange(of: geo.frame(in: .global).midY) { midY in - scrollAtTop = midY >= -60.0 + scrollAtTop = midY >= -30.0 } } .frame(width: 0, height: 0) - VStack { - ZStack { - Rectangle() - .frame(height: 150) - .foregroundColor(Color("ALUM Primary Purple")) - .padding(.bottom, 76) - Group { - Circle() - .frame(width: 135, height: 145) - .foregroundColor(Color("ALUM White2")) - Image("ALUMLogoBlue") - .resizable() - .frame(width: 135, height: 135) - .clipShape(Circle()) - .scaledToFit() - } - .padding(.top, 57) - } - Text(mentee.name) - .font(Font.custom("Metropolis-Regular", size: 34, relativeTo: .largeTitle)) - } - HStack { - Image(systemName: "graduationcap") - .frame(width: 25.25, height: 11) - .foregroundColor(Color("ALUM Primary Purple")) - Text(String(mentee.grade) + "th Grade @ NHS") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .padding(.bottom, 18) - Text("About") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.bottom, 8) - Text(mentee.about ) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .lineSpacing(5) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.bottom, 32) - RenderTags(tags: mentee.careerInterests, title: "Career Interests") - .padding(.leading, 16) - .padding(.bottom, 8) - RenderTags(tags: mentee.topicsOfInterest, title: "Topics of Interest") - .padding(.leading, 16) - .padding(.bottom, 8) + header + description if viewModel.selfView! { - Text("My Mentor") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 16) - .padding(.leading, 16) - .padding(.bottom, 8) - NavigationLink(destination: - MentorProfileScreen( - uID: mentee.mentorId ?? "", - prevView: AnyView(MenteeProfileScreen(uID: uID) - .onDisappear(perform: { - self.currentUser.showTabBar = false - }) - .onAppear(perform: { - self.currentUser.showTabBar = true - })) - ) - ) { - MentorCard(isEmpty: true, uID: mentee.mentorId ?? "") - .padding(.bottom, 10) - } + mentor } } .frame(minHeight: grr.size.height - 50) @@ -128,28 +63,8 @@ struct MenteeProfileScreen: View { .edgesIgnoringSafeArea(.bottom) } ZStack { - if !viewModel.selfView! { + if viewModel.selfView! { // params currently placeholders for later navigation - if scrollAtTop { - NavigationHeaderComponent( - backText: "Back", - backDestination: prevView, - title: "Mentee Profile", - purple: true, - showButton: true - ) - .background(Color("ALUM Primary Purple")) - } else { - NavigationHeaderComponent( - backText: "Back", - backDestination: prevView, - title: "Mentee Profile", - purple: false, - showButton: true - ) - .background(.white) - } - } else { if scrollAtTop { ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) .background(Color("ALUM Primary Purple")) @@ -158,11 +73,101 @@ struct MenteeProfileScreen: View { .background(.white) } } + else { + if scrollAtTop { + Rectangle() + .frame(height: 10) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxHeight: .infinity, alignment: .top) + } + } } } } } +extension MenteeProfileScreen { + private var header: some View { + VStack { + ZStack { + Rectangle() + .frame(height: 130) + .foregroundColor(Color("ALUM Primary Purple")) + .padding(.bottom, 76) + Group { + Circle() + .frame(width: 135, height: 145) + .foregroundColor(Color("ALUM White2")) + Image("ALUMLogoBlue") + .resizable() + .frame(width: 135, height: 135) + .clipShape(Circle()) + .scaledToFit() + } + .padding(.top, 57) + } + Text(viewModel.mentee!.name) + .font(Font.custom("Metropolis-Regular", size: 34, relativeTo: .largeTitle)) + } + } + private var description: some View { + Group { + HStack { + Image(systemName: "graduationcap") + .frame(width: 25.25, height: 11) + .foregroundColor(Color("ALUM Primary Purple")) + Text(String(viewModel.mentee!.grade) + "th Grade @ NHS") + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + } + .padding(.bottom, 18) + Text("About") + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.bottom, 8) + Text(viewModel.mentee!.about ) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .lineSpacing(5) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.bottom, 32) + RenderTags(tags: viewModel.mentee!.careerInterests, title: "Career Interests") + .padding(.leading, 16) + .padding(.bottom, 8) + RenderTags(tags: viewModel.mentee!.topicsOfInterest, title: "Topics of Interest") + .padding(.leading, 16) + .padding(.bottom, 8) + } + } + + private var mentor: some View { + Group { + Text("My Mentor") + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 16) + .padding(.leading, 16) + .padding(.bottom, 8) + CustomNavLink(destination: + MentorProfileScreen( + uID: viewModel.mentee!.mentorId ?? "" + ) + .onAppear(perform: { + currentUser.showTabBar = false + }) + .onDisappear(perform: { + currentUser.showTabBar = true + }) + .customNavigationTitle("Mentor Profile") + ) { + MentorCard(isEmpty: true, uID: viewModel.mentee!.mentorId ?? "") + .padding(.bottom, 10) + } + } + } +} struct MenteeProfileView_Previews: PreviewProvider { static var previews: some View { MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 360a30b5..3b6a2752 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -22,9 +22,12 @@ struct MentorProfileScreen: View { LoadingView(text: "MentorProfileScreen") } else { content + .customNavigationIsPurple(scrollAtTop) .navigationBarHidden(true) + .padding(.top, 0) } - }.onAppear(perform: { + } + .onAppear(perform: { Task { do { try await viewModel.fetchMentorInfo(userID: uID) @@ -33,63 +36,27 @@ struct MentorProfileScreen: View { } } }) + } var content: some View { let mentor = viewModel.mentor! - + return GeometryReader { grr in VStack(spacing: 0) { ScrollView { GeometryReader { geo in Rectangle() .frame(width: 0, height: 0) + .background(Color("ALUM Primary Purple")) .foregroundColor(Color("ALUM Primary Purple")) .onChange(of: geo.frame(in: .global).midY) { midY in - scrollAtTop = midY >= -60.0 + scrollAtTop = midY >= -30.0 } } .frame(width: 0, height: 0) - VStack { - ZStack { - Rectangle() - .frame(height: 150) - .foregroundColor(Color("ALUM Primary Purple")) - .padding(.bottom, 76) - Group { - Circle() - .frame(width: 135, height: 145) - .foregroundColor(Color("ALUM White2")) - Image("ALUMLogoBlue") - .resizable() - .frame(width: 135, height: 135) - .clipShape(Circle()) - .scaledToFit() - } - .padding(.top, 57) - } - Text(mentor.name) - .font(Font.custom("Metropolis-Regular", size: 34, relativeTo: .largeTitle)) - } - HStack { - Image(systemName: "graduationcap") - .frame(width: 25.25, height: 11) - .foregroundColor(Color("ALUM Primary Purple")) - Text(mentor.major + " @ " + mentor.college) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .padding(.bottom, 6) - HStack { - Image(systemName: "suitcase") - .frame(width: 25.25, height: 11) - .foregroundColor(Color("ALUM Primary Purple")) - Text(mentor.career) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .padding(.bottom, 6) - Text("NHS '19") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .padding(.bottom, 6) + header + description Button { showWebView.toggle() print("Viewing Calendly") @@ -103,51 +70,9 @@ struct MentorProfileScreen: View { .buttonStyle(FilledInButtonStyle()) .frame(width: 358) .padding(.bottom, 26) - Text("About") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.bottom, 8) - Text(mentor.about) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .lineSpacing(5) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, 16) - .padding(.bottom, 32) - RenderTags(tags: mentor.topicsOfExpertise, title: "Topics of Expertise") - .padding(.leading, 16) - .padding(.bottom, 8) + about if viewModel.selfView! { - Group { - Text("My Mentees") - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.top, 27) - WrappingHStack(0 ..< mentor.menteeIds!.count, id: \.self) { index in - NavigationLink(destination: - MenteeProfileScreen( - uID: mentor.menteeIds![index], - prevView: AnyView(MentorProfileScreen(uID: uID) - .onDisappear(perform: { - self.currentUser.showTabBar = false - }) - .onAppear(perform: { - self.currentUser.showTabBar = true - })) - ) - ) { - MenteeCard(isEmpty: true, uID: mentor.menteeIds![index]) - .padding(.bottom, 15) - .padding(.trailing, 10) - } - } - .padding(.bottom, 30) - .padding(.leading, 16) - .offset(y: -20) - } + mentees } } .frame(minHeight: grr.size.height - 50) @@ -156,26 +81,8 @@ struct MentorProfileScreen: View { .edgesIgnoringSafeArea(.bottom) } ZStack { - if !viewModel.selfView! { + if viewModel.selfView! { // params currently placeholders for later navigation - if scrollAtTop { - NavigationHeaderComponent( - backText: "Back", - backDestination: prevView, - title: "Mentor Profile", - purple: true, - showButton: true) - .background(Color("ALUM Primary Purple")) - } else { - NavigationHeaderComponent( - backText: "Back", - backDestination: prevView, - title: "Mentor Profile", - purple: false, - showButton: true) - .background(.white) - } - } else { if scrollAtTop { ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) .background(Color("ALUM Primary Purple")) @@ -184,6 +91,14 @@ struct MentorProfileScreen: View { .background(.white) } } + else { + if scrollAtTop { + Rectangle() + .frame(height: 10) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxHeight: .infinity, alignment: .top) + } + } } } } @@ -194,3 +109,105 @@ struct MentorProfileScreen_Previews: PreviewProvider { MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") } } + +extension MentorProfileScreen { + private var header: some View { + VStack { + ZStack { + Rectangle() + .frame(height: 130) + .foregroundColor(Color("ALUM Primary Purple")) + .padding(.bottom, 76) + Group { + Circle() + .frame(width: 135, height: 145) + .foregroundColor(Color("ALUM White2")) + Image("ALUMLogoBlue") + .resizable() + .frame(width: 135, height: 135) + .clipShape(Circle()) + .scaledToFit() + } + .padding(.top, 57) + } + Text(viewModel.mentor!.name) + .font(Font.custom("Metropolis-Regular", size: 34, relativeTo: .largeTitle)) + } + } + + private var description: some View { + Group { + HStack { + Image(systemName: "graduationcap") + .frame(width: 25.25, height: 11) + .foregroundColor(Color("ALUM Primary Purple")) + Text(viewModel.mentor!.major + " @ " + viewModel.mentor!.college) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + } + .padding(.bottom, 6) + HStack { + Image(systemName: "suitcase") + .frame(width: 25.25, height: 11) + .foregroundColor(Color("ALUM Primary Purple")) + Text(viewModel.mentor!.career) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + } + .padding(.bottom, 6) + Text("NHS " + String(viewModel.mentor!.graduationYear)) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .padding(.bottom, 6) + } + } + + private var about: some View { + Group { + Text("About") + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.bottom, 8) + Text(viewModel.mentor!.about) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .lineSpacing(5) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + .padding(.bottom, 32) + RenderTags(tags: viewModel.mentor!.topicsOfExpertise, title: "Topics of Expertise") + .padding(.leading, 16) + .padding(.bottom, 8) + } + } + + private var mentees: some View { + Group { + Text("My Mentees") + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.top, 27) + WrappingHStack(0 ..< viewModel.mentor!.menteeIds!.count, id: \.self) { index in + CustomNavLink(destination: + MenteeProfileScreen( + uID: viewModel.mentor!.menteeIds![index] + ) + .customNavigationTitle("Mentee Profile") + .onAppear(perform: { + currentUser.showTabBar = false + }) + .onDisappear(perform: { + currentUser.showTabBar = true + }) + ) { + MenteeCard(isEmpty: true, uID: viewModel.mentor!.menteeIds![index]) + .padding(.bottom, 15) + .padding(.trailing, 10) + } + } + .padding(.bottom, 30) + .padding(.leading, 16) + .offset(y: -20) + } + } +} From 0a5a849754bf9310883975934f81aa3180a9dbba Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 20 May 2023 23:27:13 -0700 Subject: [PATCH 05/33] organize routing code to Views/Router/ --- ALUM/ALUM.xcodeproj/project.pbxproj | 14 ++- ALUM/ALUM/ALUMApp.swift | 23 +--- .../Views/{ => Routers}/LoggedInRouter.swift | 102 ++++++++++-------- ALUM/ALUM/Views/Routers/RootRouter.swift | 35 ++++++ 4 files changed, 104 insertions(+), 70 deletions(-) rename ALUM/ALUM/Views/{ => Routers}/LoggedInRouter.swift (57%) create mode 100644 ALUM/ALUM/Views/Routers/RootRouter.swift diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 697a3a6f..e2da7c6d 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -40,6 +40,7 @@ 0799ACE52A00924F00EEAFA2 /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */; }; 0799ACE92A00A6C200EEAFA2 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */; }; 0799ACF22A01109100EEAFA2 /* LoginReviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */; }; + 07E885362A19F0D300B7AD27 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E885352A19F0D300B7AD27 /* RootRouter.swift */; }; 2A638880299FF3BC00F9EA97 /* DrawerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */; }; 2A8E07E2297B4FB0001AA153 /* OutlinedButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */; }; 2ADCE5BF299DBD570069802F /* ParagraphInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ADCE5BE299DBD570069802F /* ParagraphInput.swift */; }; @@ -157,6 +158,7 @@ 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInRouter.swift; sourceTree = ""; }; 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginReviewPage.swift; sourceTree = ""; }; + 07E885352A19F0D300B7AD27 /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerContainer.swift; sourceTree = ""; }; 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutlinedButtonStyle.swift; sourceTree = ""; }; 2ADCE5BE299DBD570069802F /* ParagraphInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParagraphInput.swift; sourceTree = ""; }; @@ -331,6 +333,15 @@ name = "Recovered References"; sourceTree = ""; }; + 07E885342A19F0B800B7AD27 /* Routers */ = { + isa = PBXGroup; + children = ( + 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */, + 07E885352A19F0D300B7AD27 /* RootRouter.swift */, + ); + path = Routers; + sourceTree = ""; + }; 07F9A50C297180D700BC11A8 /* Components */ = { isa = PBXGroup; children = ( @@ -387,6 +398,7 @@ isa = PBXGroup; children = ( 979303D029E7EA680053C30E /* SessionDetailsView */, + 07E885342A19F0B800B7AD27 /* Routers */, 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */, 97A31AC729BAFA7600B79D30 /* PreSessionForm */, 97A2FE8B2989C20900405FD6 /* LoginScreen.swift */, @@ -396,7 +408,6 @@ 97992B5D29A6E7E200701CC7 /* SignUpPage */, 0748209029712921004AF547 /* ContentView.swift */, 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */, - 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */, ); path = Views; sourceTree = ""; @@ -677,6 +688,7 @@ 0726C9EF29DC4B120042A486 /* CustomErrors.swift in Sources */, E3F4186629E7246400FDE56D /* CalendlyBooking.swift in Sources */, 97E439F829AD986A00F0B7C1 /* SignUpJoinOption.swift in Sources */, + 07E885362A19F0D300B7AD27 /* RootRouter.swift in Sources */, 0799ACF22A01109100EEAFA2 /* LoginReviewPage.swift in Sources */, 0757552329FAA2AB008E73FB /* CustomAlertView.swift in Sources */, 2A8E07E2297B4FB0001AA153 /* OutlinedButtonStyle.swift in Sources */, diff --git a/ALUM/ALUM/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index d84134d7..0b98aef3 100644 --- a/ALUM/ALUM/ALUMApp.swift +++ b/ALUM/ALUM/ALUMApp.swift @@ -18,27 +18,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { } } -struct RootView: View { - @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - - var body: some View { - if self.currentUser.isLoading { - LoadingView(text: "RootView") - .onAppear(perform: { - Task { - await self.currentUser.setForInSessionUser() - } - }) - } else if self.currentUser.isLoggedIn == false { - NavigationView { - LoginScreen() - } - } else { - MentorSessionDetailsPage() - } - } -} - @main struct ALUMApp: App { // register app delegate for Firebase setup @@ -46,7 +25,7 @@ struct ALUMApp: App { var body: some Scene { WindowGroup { - RootView() + RootRouter() } } } diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift similarity index 57% rename from ALUM/ALUM/Views/LoggedInRouter.swift rename to ALUM/ALUM/Views/Routers/LoggedInRouter.swift index 2392466b..038db4f8 100644 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -51,7 +51,7 @@ struct LoggedInRouter: View { TabBarItem(iconName: "ALUM Home", title: "Home"), TabBarItem(iconName: "GrayCircle", title: "Profile") ] - + var body: some View { if currentUser.status == "under review" { if currentUser.role == .mentee { @@ -74,58 +74,66 @@ struct LoggedInRouter: View { "We are looking for a perfect mentee for you. Please allow us some time!"]) } } else { - VStack(spacing: 0) { - switch selection { - case 0: - PlaceHolderHomeScreen() - .onAppear(perform: { - currentUser.showTabBar = true - }) - case 1: - ProfileRouter() - .onAppear(perform: { - currentUser.showTabBar = true - }) - default: - Text("Error") - } - if currentUser.showTabBar { - ZStack(alignment: .bottom) { - HStack(spacing: 0) { - ForEach(0.. Date: Sun, 21 May 2023 00:29:56 -0700 Subject: [PATCH 06/33] fix mentor route where zoomLink is used instead of location but location exists on the document --- backend/src/models/mentor.ts | 4 ---- backend/src/routes/user.ts | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/backend/src/models/mentor.ts b/backend/src/models/mentor.ts index 6d11360c..89fd9f2f 100644 --- a/backend/src/models/mentor.ts +++ b/backend/src/models/mentor.ts @@ -64,10 +64,6 @@ const mentorSchema = new mongoose.Schema({ type: String, required: true, }, - zoomLink: { - type: String, - required: true, - }, graduationYear: { type: Number, required: true, diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 5f1f52de..31497abb 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -269,7 +269,7 @@ router.get( career, graduationYear, calendlyLink, - zoomLink, + location, topicsOfExpertise, pairingIds, mentorMotivation, @@ -300,7 +300,7 @@ router.get( career, graduationYear, calendlyLink, - zoomLink, + zoomLink: location, topicsOfExpertise, whyPaired, }, @@ -319,7 +319,7 @@ router.get( imageId, about, calendlyLink, - zoomLink, + zoomLink: location, graduationYear, college, major, @@ -339,7 +339,7 @@ router.get( imageId, about, calendlyLink, - zoomLink: zoomLink === undefined ? "N/A" : zoomLink, + location: location ?? "N/A", graduationYear, college, major, From 06fdd47d60dd27d24c3070f91246ce325cf3a657 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 20 May 2023 15:39:35 -0700 Subject: [PATCH 07/33] fix errors from pr #102 that prevented npm run dev --- backend/src/errors/service.ts | 7 ++++--- backend/src/models/session.ts | 2 -- backend/src/routes/notes.ts | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 08dd65df..399b2786 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -15,7 +15,6 @@ const IMAGE_WAS_NOT_FOUND = "Image was not found"; const NOTE_WAS_NOT_FOUND = "Note was not found"; const NOTE_WAS_NOT_SAVED = "Note was not saved"; const SESSION_WAS_NOT_FOUND = "Session was not found"; -const NOTE_WAS_NOT_FOUND = "Note was not found"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; @@ -36,7 +35,9 @@ export class ServiceError extends CustomError { static ERROR_GETTING_EVENT_DATA = new ServiceError(7, 404, ERROR_GETTING_EVENT_DATA); - static SESSION_WAS_NOT_FOUND = new ServiceError(6, 404, SESSION_WAS_NOT_FOUND); + static SESSION_WAS_NOT_FOUND = new ServiceError(8, 404, SESSION_WAS_NOT_FOUND); + + static NOTE_WAS_NOT_FOUND = new ServiceError(9, 404, NOTE_WAS_NOT_FOUND); - static NOTE_WAS_NOT_FOUND = new ServiceError(7, 404, NOTE_WAS_NOT_FOUND); + static NOTE_WAS_NOT_SAVED = new ServiceError(10, 404, NOTE_WAS_NOT_SAVED); } diff --git a/backend/src/models/session.ts b/backend/src/models/session.ts index 851cea89..227a3b00 100644 --- a/backend/src/models/session.ts +++ b/backend/src/models/session.ts @@ -12,8 +12,6 @@ interface SessionInterface { menteeId: ObjectId; mentorId: ObjectId; dateTime: Date; - menteeId: string; - mentorId: string; startTime: Date; endTime: Date; calendlyUri: string; diff --git a/backend/src/routes/notes.ts b/backend/src/routes/notes.ts index 484eadba..adbf4778 100644 --- a/backend/src/routes/notes.ts +++ b/backend/src/routes/notes.ts @@ -49,7 +49,6 @@ router.get("/notes/:id", async (req: Request, res: Response, next: NextFunction) note_answer.question = questionIDs.get(note_answer.id) ?? ""; }); return res.status(200).json(note.answers); - res.status(200).json(note.answers); } catch (e) { next(e); } From 338f8eac542bf08bfe6d3b2a3182086675eba6a7 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 21 May 2023 01:30:54 -0700 Subject: [PATCH 08/33] fix discrpencies between use of location and zoomlink in backend and improve error handling for debug messages --- ALUM/ALUM/CustomErrors.swift | 19 +++++++++++++++++++ ALUM/ALUM/Services/NotesService.swift | 1 - ALUM/ALUM/Services/UserService.swift | 14 ++++++++------ ALUM/ALUM/ViewModels/LoginViewModel.swift | 2 +- backend/src/routes/user.ts | 2 +- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ALUM/ALUM/CustomErrors.swift b/ALUM/ALUM/CustomErrors.swift index 644c4001..60ef88e3 100644 --- a/ALUM/ALUM/CustomErrors.swift +++ b/ALUM/ALUM/CustomErrors.swift @@ -25,3 +25,22 @@ enum InternalError: Error { case unknownError case jsonParsingError } + +func handleDecodingErrors(_ decodingClosure: () throws -> T) throws -> T { + var errorMessage: String + do { + return try decodingClosure() + } catch let DecodingError.dataCorrupted(context) { + errorMessage = "context: \(context)" + } catch let DecodingError.keyNotFound(key, context) { + errorMessage = "Key '\(key)' not found, context: \(context.debugDescription)" + } catch let DecodingError.valueNotFound(value, context) { + errorMessage = "Value '\(value)' not found, context: \(context.debugDescription)" + } catch let DecodingError.typeMismatch(type, context) { + errorMessage = "Type '\(type)' mismatch:, context: \(context.debugDescription)" + } catch { + errorMessage = "Unknown: \(error)" + } + + throw AppError.internalError(.invalidResponse, message: "Decode error - \(errorMessage)") +} diff --git a/ALUM/ALUM/Services/NotesService.swift b/ALUM/ALUM/Services/NotesService.swift index c9adc5e1..c4771827 100644 --- a/ALUM/ALUM/Services/NotesService.swift +++ b/ALUM/ALUM/Services/NotesService.swift @@ -61,7 +61,6 @@ class NotesService { static let shared = NotesService() func patchNotes(noteId: String, data: [QuestionPatchData]) async throws { - print(data.count) let route = APIRoute.patchNote(noteId: noteId) var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(data) else { diff --git a/ALUM/ALUM/Services/UserService.swift b/ALUM/ALUM/Services/UserService.swift index 2e9cc6f5..a6cc4365 100644 --- a/ALUM/ALUM/Services/UserService.swift +++ b/ALUM/ALUM/Services/UserService.swift @@ -74,9 +74,9 @@ class UserService { let route = APIRoute.getMentor(userId: userID) let request = try await route.createURLRequest() let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) - guard let mentorData = try? JSONDecoder().decode(MentorGetData.self, from: responseData) else { - throw AppError.internalError(.invalidResponse, message: "Failed to Decode Data") - } + let mentorData = try handleDecodingErrors({ + try JSONDecoder().decode(MentorGetData.self, from: responseData) + }) print("SUCCESS - \(route.label)") return mentorData } @@ -85,9 +85,11 @@ class UserService { let route = APIRoute.getMentee(userId: userID) let request = try await route.createURLRequest() let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) - guard let menteeData = try? JSONDecoder().decode(MenteeGetData.self, from: responseData) else { - throw AppError.internalError(.invalidResponse, message: "Failed to Decode Data") - } + + let menteeData = try handleDecodingErrors({ + try JSONDecoder().decode(MenteeGetData.self, from: responseData) + }) + print("SUCCESS - \(route.label)") return menteeData } diff --git a/ALUM/ALUM/ViewModels/LoginViewModel.swift b/ALUM/ALUM/ViewModels/LoginViewModel.swift index 6857a4b7..7ef98eb9 100644 --- a/ALUM/ALUM/ViewModels/LoginViewModel.swift +++ b/ALUM/ALUM/ViewModels/LoginViewModel.swift @@ -32,7 +32,7 @@ final class LoginViewModel: ObservableObject { case .userNotFound: self.emailFunc = [Functions.IncorrectEmail] default: - print("Some unknown error happened") + print("error LoginViewModel::login - \(error.description)") } } } diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 31497abb..60ddb980 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -339,7 +339,7 @@ router.get( imageId, about, calendlyLink, - location: location ?? "N/A", + zoomLink: location ?? "N/A", graduationYear, college, major, From 4afa0b396407f119ee22b4876d7a4c2e3f12189f Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Mon, 22 May 2023 17:28:29 -0700 Subject: [PATCH 09/33] update previews for mentor and mentee profile screens --- ALUM/ALUM/Views/MenteeProfileScreen.swift | 37 ++++++++++------------- ALUM/ALUM/Views/MentorProfileScreen.swift | 21 ++++++++----- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index 50686920..b5489405 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -62,26 +62,6 @@ struct MenteeProfileScreen: View { .padding(.bottom, 8) .edgesIgnoringSafeArea(.bottom) } - ZStack { - if viewModel.selfView! { - // params currently placeholders for later navigation - if scrollAtTop { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) - .background(Color("ALUM Primary Purple")) - } else { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) - .background(.white) - } - } - else { - if scrollAtTop { - Rectangle() - .frame(height: 10) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxHeight: .infinity, alignment: .top) - } - } - } } } } @@ -168,8 +148,23 @@ extension MenteeProfileScreen { } } } +//struct MenteeProfileView_Previews: PreviewProvider { +// static var previews: some View { +// MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") +// } +//} +// + struct MenteeProfileView_Previews: PreviewProvider { static var previews: some View { - MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentor, status: "paired") + return CustomNavView { + MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") + .onAppear { + Task { + try await FirebaseAuthenticationService.shared.login(email: "mentor@gmail.com", password: "123456") + } + } + } } } diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 3b6a2752..862953d1 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -36,7 +36,6 @@ struct MentorProfileScreen: View { } } }) - } var content: some View { @@ -104,12 +103,6 @@ struct MentorProfileScreen: View { } } -struct MentorProfileScreen_Previews: PreviewProvider { - static var previews: some View { - MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") - } -} - extension MentorProfileScreen { private var header: some View { VStack { @@ -211,3 +204,17 @@ extension MentorProfileScreen { } } } + +struct MentorProfileScreen_Previews: PreviewProvider { + static var previews: some View { + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor, status: "paired") + return CustomNavView { + MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") + .onAppear { + Task { + try await FirebaseAuthenticationService.shared.login(email: "mentor@gmail.com", password: "123456") + } + } + } + } +} From 8abdb64d3c87050ebbfb0b73df0a66a5b8f78594 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 00:03:19 -0700 Subject: [PATCH 10/33] Combine Mentor and Mentee Session Pages into one --- ALUM/ALUM.xcodeproj/project.pbxproj | 30 +- .../CustomNavBarPreferenceKeys.swift | 2 +- .../Components/HorizontalMenteeCard.swift | 46 +- ALUM/ALUM/Constants/ALUMColor.swift | 51 +++ ALUM/ALUM/Constants/ALUMText.swift | 42 ++ ALUM/ALUM/Constants/DevelopmentModels.swift | 43 ++ ALUM/ALUM/Models/SessionModel.swift | 19 + ALUM/ALUM/Services/SessionService.swift | 33 +- .../ViewModels/MenteeProfileViewModel.swift | 2 +- .../ViewModels/SessionDetailViewModel.swift | 87 +--- .../PreSessionForm/PostSessionView.swift | 2 +- .../SessionConfirmationScreen.swift | 2 +- .../MenteeSessionsDetailsPage.swift | 272 ------------ .../MentorSessionDetailsPage.swift | 247 ----------- .../SessionDetailsScreen.swift | 415 ++++++++++++++++++ .../ViewPreSessionNotesPage.swift | 16 +- backend/src/routes/sessions.ts | 38 +- backend/src/services/session.ts | 49 +++ 18 files changed, 734 insertions(+), 662 deletions(-) create mode 100644 ALUM/ALUM/Constants/ALUMColor.swift create mode 100644 ALUM/ALUM/Constants/ALUMText.swift create mode 100644 ALUM/ALUM/Constants/DevelopmentModels.swift delete mode 100644 ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift delete mode 100644 ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift create mode 100644 ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift create mode 100644 backend/src/services/session.ts diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index e2da7c6d..25e4e523 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -40,6 +40,10 @@ 0799ACE52A00924F00EEAFA2 /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */; }; 0799ACE92A00A6C200EEAFA2 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */; }; 0799ACF22A01109100EEAFA2 /* LoginReviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */; }; + 07A565C82A1C3806008C96BC /* SessionDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */; }; + 07CA49932A1C50CC00A81153 /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */; }; + 07CA49962A1C53DA00A81153 /* ALUMColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49942A1C53DA00A81153 /* ALUMColor.swift */; }; + 07CA49972A1C53DA00A81153 /* ALUMText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49952A1C53DA00A81153 /* ALUMText.swift */; }; 07E885362A19F0D300B7AD27 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E885352A19F0D300B7AD27 /* RootRouter.swift */; }; 2A638880299FF3BC00F9EA97 /* DrawerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */; }; 2A8E07E2297B4FB0001AA153 /* OutlinedButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */; }; @@ -95,8 +99,6 @@ 979303CB29E7E9460053C30E /* SessionDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303CA29E7E9460053C30E /* SessionDetailViewModel.swift */; }; 979303D629E872190053C30E /* FormIncompleteComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303D529E872190053C30E /* FormIncompleteComponent.swift */; }; 979303D829E87E0E0053C30E /* SessionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303D729E87E0E0053C30E /* SessionModel.swift */; }; - 979303DA29E882E20053C30E /* MentorSessionDetailsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303D929E882E20053C30E /* MentorSessionDetailsPage.swift */; }; - 979303DC29E885140053C30E /* MenteeSessionsDetailsPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303DB29E885140053C30E /* MenteeSessionsDetailsPage.swift */; }; 979303DE29E885990053C30E /* HorizontalMenteeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303DD29E885990053C30E /* HorizontalMenteeCard.swift */; }; 979303E029E8B06E0053C30E /* SessionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 979303DF29E8B06E0053C30E /* SessionService.swift */; }; 97992B5F29A6E81D00701CC7 /* SignUpModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97992B5E29A6E81D00701CC7 /* SignUpModel.swift */; }; @@ -120,7 +122,6 @@ 97F1DB6C2A09DD4600DE8DB4 /* SessionButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F1DB6B2A09DD4600DE8DB4 /* SessionButtonComponent.swift */; }; 97F6CDF429BD317200DFBB99 /* TopicsOfInterest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F6CDF329BD317200DFBB99 /* TopicsOfInterest.swift */; }; 97F6CDF629BD4E3D00DFBB99 /* CareerInterests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97F6CDF529BD4E3D00DFBB99 /* CareerInterests.swift */; }; - E365F71E29FA0D110055A2F4 /* SessionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E365F71D29FA0D110055A2F4 /* SessionService.swift */; }; E3F4186629E7246400FDE56D /* CalendlyBooking.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3F4186529E7246400FDE56D /* CalendlyBooking.swift */; }; /* End PBXBuildFile section */ @@ -158,6 +159,10 @@ 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInRouter.swift; sourceTree = ""; }; 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginReviewPage.swift; sourceTree = ""; }; + 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailsScreen.swift; sourceTree = ""; }; + 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = ""; }; + 07CA49942A1C53DA00A81153 /* ALUMColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALUMColor.swift; sourceTree = ""; }; + 07CA49952A1C53DA00A81153 /* ALUMText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALUMText.swift; sourceTree = ""; }; 07E885352A19F0D300B7AD27 /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerContainer.swift; sourceTree = ""; }; 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutlinedButtonStyle.swift; sourceTree = ""; }; @@ -195,8 +200,6 @@ 979303CA29E7E9460053C30E /* SessionDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailViewModel.swift; sourceTree = ""; }; 979303D529E872190053C30E /* FormIncompleteComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormIncompleteComponent.swift; sourceTree = ""; }; 979303D729E87E0E0053C30E /* SessionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionModel.swift; sourceTree = ""; }; - 979303D929E882E20053C30E /* MentorSessionDetailsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentorSessionDetailsPage.swift; sourceTree = ""; }; - 979303DB29E885140053C30E /* MenteeSessionsDetailsPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenteeSessionsDetailsPage.swift; sourceTree = ""; }; 979303DD29E885990053C30E /* HorizontalMenteeCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalMenteeCard.swift; sourceTree = ""; }; 979303DF29E8B06E0053C30E /* SessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionService.swift; sourceTree = ""; }; 97992B5E29A6E81D00701CC7 /* SignUpModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpModel.swift; sourceTree = ""; }; @@ -237,7 +240,6 @@ 97F1DB6B2A09DD4600DE8DB4 /* SessionButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionButtonComponent.swift; sourceTree = ""; }; 97F6CDF329BD317200DFBB99 /* TopicsOfInterest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsOfInterest.swift; sourceTree = ""; }; 97F6CDF529BD4E3D00DFBB99 /* CareerInterests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CareerInterests.swift; sourceTree = ""; }; - E365F71D29FA0D110055A2F4 /* SessionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionService.swift; sourceTree = ""; }; E3F4186529E7246400FDE56D /* CalendlyBooking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendlyBooking.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -434,7 +436,6 @@ 979303DF29E8B06E0053C30E /* SessionService.swift */, 8045EEB029F6844A00BD179C /* ServiceHelper.swift */, 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */, - E365F71D29FA0D110055A2F4 /* SessionService.swift */, ); path = Services; sourceTree = ""; @@ -442,10 +443,9 @@ 979303D029E7EA680053C30E /* SessionDetailsView */ = { isa = PBXGroup; children = ( - 979303D929E882E20053C30E /* MentorSessionDetailsPage.swift */, - 979303DB29E885140053C30E /* MenteeSessionsDetailsPage.swift */, - 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, + 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */, 974D62D029FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift */, + 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, ); path = SessionDetailsView; sourceTree = ""; @@ -508,8 +508,11 @@ 97EB2BAD29B59CF5001988D9 /* Constants */ = { isa = PBXGroup; children = ( + 07CA49942A1C53DA00A81153 /* ALUMColor.swift */, + 07CA49952A1C53DA00A81153 /* ALUMText.swift */, 0726C9ED29DC4AE20042A486 /* UserApplicationConstants */, 97EB2BAE29B59D08001988D9 /* ErrorFunctions */, + 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */, ); path = Constants; sourceTree = ""; @@ -654,6 +657,8 @@ 0757553C29FAA387008E73FB /* NotesService.swift in Sources */, 0794653429DC536200B68EFD /* InputValidationComponent.swift in Sources */, 8095FE7229E41214006AA63C /* NavigationComponent.swift in Sources */, + 07CA49932A1C50CC00A81153 /* DevelopmentModels.swift in Sources */, + 07CA49962A1C53DA00A81153 /* ALUMColor.swift in Sources */, 0757552029FAA2AB008E73FB /* StaticProgressBarComponent.swift in Sources */, 8071E52D2A044E2900BD11A3 /* CurrentUserModel.swift in Sources */, 2A638880299FF3BC00F9EA97 /* DrawerContainer.swift in Sources */, @@ -669,7 +674,6 @@ 8041161E29B21BB800529936 /* MentorProfileScreen.swift in Sources */, 8095FE7029E20C89006AA63C /* WhyPairedComponent.swift in Sources */, 0793B41E2A19FA7200AF78C8 /* CustomNavBarContainerView.swift in Sources */, - E365F71E29FA0D110055A2F4 /* SessionService.swift in Sources */, 97E439F229AD97B100F0B7C1 /* SignUpJoinAsScreen.swift in Sources */, 979303D629E872190053C30E /* FormIncompleteComponent.swift in Sources */, 0757552F29FAA2D6008E73FB /* PreSessionView.swift in Sources */, @@ -677,13 +681,13 @@ 97EB2BB029B59D19001988D9 /* SignUpFlowErrorFunctions.swift in Sources */, 8095FE7529E4153C006AA63C /* ProfileHeaderComponent.swift in Sources */, 9756DF752982338300A0BCB5 /* InputValidationText.swift in Sources */, - 979303DA29E882E20053C30E /* MentorSessionDetailsPage.swift in Sources */, 97886D0F29C1964B00548A12 /* ResizingTextBox.swift in Sources */, 0757552129FAA2AB008E73FB /* Bullet.swift in Sources */, 97E439F029AD95FA00F0B7C1 /* SignUpSetUpScreen.swift in Sources */, 804AE4AD297B1DA4000B08F2 /* FilledInButtonStyle.swift in Sources */, 0757552429FAA2AB008E73FB /* MultipleChoiceList.swift in Sources */, 0757553129FAA2D6008E73FB /* PostSessionView.swift in Sources */, + 07CA49972A1C53DA00A81153 /* ALUMText.swift in Sources */, 0757553029FAA2D6008E73FB /* PreSessionConfirmationScreen.swift in Sources */, 0726C9EF29DC4B120042A486 /* CustomErrors.swift in Sources */, E3F4186629E7246400FDE56D /* CalendlyBooking.swift in Sources */, @@ -734,8 +738,8 @@ 80210EC529BA8255008B912A /* MenteeProfileScreen.swift in Sources */, 0748208F29712921004AF547 /* ALUMApp.swift in Sources */, 80210EBD29B95D50008B912A /* ProfileModel.swift in Sources */, - 979303DC29E885140053C30E /* MenteeSessionsDetailsPage.swift in Sources */, 97A2FE8C2989C20900405FD6 /* LoginScreen.swift in Sources */, + 07A565C82A1C3806008C96BC /* SessionDetailsScreen.swift in Sources */, 80210ED429C3DBD9008B912A /* FirebaseAuthenticationService.swift in Sources */, 0793B4212A19FA7200AF78C8 /* CustomNavBarView.swift in Sources */, 80F5388C29B0252F00FB5E66 /* UserService.swift in Sources */, diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift index 5846c82b..b5c85c64 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -48,7 +48,7 @@ extension View { preference(key: CustomNavBarBackButtonHiddenPreferenceKey.self, value: hidden) } - func customNavBarItems(title: String = "", isPurple: Bool = true, backButtonHidden: Bool = false) -> some View { + func customNavBarItems(title: String, isPurple: Bool, backButtonHidden: Bool) -> some View { self .customNavigationTitle(title) .customNavigationIsPurple(isPurple) diff --git a/ALUM/ALUM/Components/HorizontalMenteeCard.swift b/ALUM/ALUM/Components/HorizontalMenteeCard.swift index ac35b071..e488c01f 100644 --- a/ALUM/ALUM/Components/HorizontalMenteeCard.swift +++ b/ALUM/ALUM/Components/HorizontalMenteeCard.swift @@ -8,26 +8,50 @@ import SwiftUI struct HorizontalMenteeCard: View { - @State var name: String = "Mentee Name" - @State var grade: Int = 9 - @State var school: String = "NHS" - @State var profilePic: Image = Image("TestMenteePFP") + var menteeId: String + + var school: String = "NHS" @State var isEmpty = true - + @StateObject private var viewModel = MenteeProfileViewmodel() + var body: some View { - Button { + loadingAbstraction + } + + var loadingAbstraction: some View { + Group { + if viewModel.isLoading() || viewModel.mentee == nil { + LoadingView(text: "HorizontalMenteeCard") + } else { + loadedView + } + }.onAppear(perform: { + Task { + do { + try await viewModel.fetchMenteeInfo(userID: menteeId) + } catch { + print("Error") + } + } + }) + } + + var loadedView: some View { + let mentee = viewModel.mentee! + + return Button { } label: { ZStack { RoundedRectangle(cornerRadius: 12.0) .frame(width: 358, height: 118) - .foregroundColor(Color("ALUM Dark Blue")) + .foregroundColor(Color("ALUM Primary Purple")) if isEmpty { Circle() .frame(width: 85) .foregroundColor(Color("NeutralGray1")) .offset(x: -112.5) } else { - profilePic + Image(mentee.imageId) .resizable() .clipShape(Circle()) .frame(width: 85, height: 85) @@ -35,7 +59,7 @@ struct HorizontalMenteeCard: View { } VStack { HStack { - Text(name) + Text(mentee.name) .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) .foregroundColor(.white) Spacer() @@ -47,7 +71,7 @@ struct HorizontalMenteeCard: View { .resizable() .frame(width: 19, height: 18) .foregroundColor(.white) - Text(String(grade) + "th Grade" + " @ " + school) + Text(String(mentee.grade) + "th Grade" + " @ " + school) .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) .foregroundColor(.white) .frame(width: 200, alignment: .leading) @@ -63,6 +87,6 @@ struct HorizontalMenteeCard: View { struct HorizontalMenteeCard_Previews: PreviewProvider { static var previews: some View { - HorizontalMenteeCard() + HorizontalMenteeCard(menteeId: "6431b99ebcf4420fe9825fe3") } } diff --git a/ALUM/ALUM/Constants/ALUMColor.swift b/ALUM/ALUM/Constants/ALUMColor.swift new file mode 100644 index 00000000..feef8349 --- /dev/null +++ b/ALUM/ALUM/Constants/ALUMColor.swift @@ -0,0 +1,51 @@ +// +// ALUMColors.swift +// ALUM +// +// Created by Aman Aggarwal on 4/23/23. +// + +import SwiftUI + +extension Color { + init(hex: UInt, alpha: Double = 1) { + self.init( + .sRGB, + red: Double((hex >> 16) & 0xff) / 255, + green: Double((hex >> 08) & 0xff) / 255, + blue: Double((hex >> 00) & 0xff) / 255, + opacity: alpha + ) + } +} + +enum ALUMColor: UInt { + case lightBlue = 0xD0DBFF + case primaryBlue = 0x4470FF + + case extraLightPurple = 0xEFEEFF + case lightPurple = 0xD0CDFF + case primaryPurple = 0x463EC7 + + case red = 0xB93B3B + case green = 0x3BB966 + + case gray1 = 0xEBEBEB + case gray2 = 0xD8D8D8 + case gray3 = 0xB4B4B4 + case gray4 = 0x909090 + + case white = 0xFFFFFF + case beige = 0xFCFAF6 // used as background color in most places + case black = 0x000000 + + var color: Color { + return Color(hex: self.rawValue) + } +} + +let primaryGradientColor = LinearGradient( + gradient: Gradient(colors: [ALUMColor.primaryBlue.color, ALUMColor.primaryPurple.color]), + startPoint: .bottomTrailing, + endPoint: .topLeading +) diff --git a/ALUM/ALUM/Constants/ALUMText.swift b/ALUM/ALUM/Constants/ALUMText.swift new file mode 100644 index 00000000..b208e1e8 --- /dev/null +++ b/ALUM/ALUM/Constants/ALUMText.swift @@ -0,0 +1,42 @@ +// +// File.swift +// ALUM +// +// Created by Aman Aggarwal on 4/23/23. +// + +import SwiftUI + +enum ALUMFontName: String { + case bodyFontName = "Metropolis-Regular" +} + +enum ALUMFontSize: CGFloat { + case smallFontSize = 13 + case bodyFontSize = 17 + case largeFontSize = 34 +} + +struct ALUMText: View { + let text: String + let fontName: ALUMFontName + let fontSize: ALUMFontSize + let textColor: ALUMColor + let isUnderlined: Bool + + init(text: String, fontName: ALUMFontName = .bodyFontName, + fontSize: ALUMFontSize = .bodyFontSize, textColor: ALUMColor = ALUMColor.primaryPurple, isUnderlined: Bool = false) { + self.text = text + self.fontName = fontName + self.fontSize = fontSize + self.isUnderlined = isUnderlined + self.textColor = textColor + } + + var body: some View { + Text(text) + .font(.custom(fontName.rawValue, size: fontSize.rawValue)) + .underline(isUnderlined) + .foregroundColor(textColor.color) + } +} diff --git a/ALUM/ALUM/Constants/DevelopmentModels.swift b/ALUM/ALUM/Constants/DevelopmentModels.swift new file mode 100644 index 00000000..32da3810 --- /dev/null +++ b/ALUM/ALUM/Constants/DevelopmentModels.swift @@ -0,0 +1,43 @@ +// +// DevelopmentModels.swift +// ALUM +// +// Created by Aman Aggarwal on 5/22/23. +// + +import Foundation + +class DevelopmentModels { + static var sessionModel: SessionModel = SessionModel( + preSession: "6464276b6f05d9703f069761", + postSessionMentee: Optional("6464276b6f05d9703f069763"), + postSessionMentor: Optional("6464276b6f05d9703f069765"), + menteeId: "6431b99ebcf4420fe9825fe3", + mentorId: "6431b9a2bcf4420fe9825fe5", + menteeName: "Mentee Name", + mentorName: "Mentor Name", + fullDateString: "Thursday, May 18, 2023", + dateShortHandString: "5/18", + startTimeString: "11:00 AM", + endTimeString: "11:30 AM", + preSessionCompleted: true, + postSessionMenteeCompleted: false, + postSessionMentorCompleted: false, + hasPassed: false, + location: "https://alum.zoom.us/my/timby" + ) + + static var menteeModel: MenteeInfo = MenteeInfo( + id: "6431b99ebcf4420fe9825fe3", + name: "Mentee", + imageId: "640b86513c48ef1b07904241", + about: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", + grade: 9, + topicsOfInterest: ["computer science"], + careerInterests: ["software development"], + mentorshipGoal: nil, + mentorId: nil, + status: nil, + whyPaired: Optional("Modified Why Paired") + ) +} diff --git a/ALUM/ALUM/Models/SessionModel.swift b/ALUM/ALUM/Models/SessionModel.swift index 7b7e8638..7ac3862a 100644 --- a/ALUM/ALUM/Models/SessionModel.swift +++ b/ALUM/ALUM/Models/SessionModel.swift @@ -21,3 +21,22 @@ struct Session { // we can get location as mentor.zoomLink } + +struct SessionModel: Decodable { + var preSession: String + var postSessionMentee: String? + var postSessionMentor: String? + var menteeId: String + var mentorId: String + var menteeName: String + var mentorName: String + var fullDateString: String + var dateShortHandString: String + var startTimeString: String + var endTimeString: String + var preSessionCompleted: Bool + var postSessionMenteeCompleted: Bool + var postSessionMentorCompleted: Bool + var hasPassed: Bool + var location: String +} diff --git a/ALUM/ALUM/Services/SessionService.swift b/ALUM/ALUM/Services/SessionService.swift index dfd8dd6e..ae457d67 100644 --- a/ALUM/ALUM/Services/SessionService.swift +++ b/ALUM/ALUM/Services/SessionService.swift @@ -26,7 +26,7 @@ struct SessionInfo: Decodable { struct GetSessionData: Decodable { var message: String - var session: SessionInfo + var session: SessionModel } struct UserSessionInfo: Decodable { @@ -56,20 +56,18 @@ struct PostSessionData: Codable { } class SessionService { + static let shared = SessionService() - func getSessionWithID(sessionID: String) async throws -> GetSessionData { - let route = APIRoute.getSession(sessionId: sessionID) + func getSessionWithId(sessionId: String) async throws -> GetSessionData { + let route = APIRoute.getSession(sessionId: sessionId) let request = try await route.createURLRequest() let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) - do { - let sessionData = try JSONDecoder().decode(GetSessionData.self, from: responseData) - print("SUCCESS - \(route.label)") - return sessionData - } catch { - print("Failed to decode data") - throw AppError.internalError(.jsonParsingError, message: "Failed to decode data") - } + let sessionData = try handleDecodingErrors({ + try JSONDecoder().decode(GetSessionData.self, from: responseData) + }) + print("SUCCESS - \(route.label)") + return sessionData } func getSessionsByUser() async throws -> GetUserSessionsData { @@ -77,14 +75,11 @@ class SessionService { let request = try await route.createURLRequest() let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) - do { - let sessionsData = try JSONDecoder().decode(GetUserSessionsData.self, from: responseData) - print("SUCCESS - \(route.label)") - return sessionsData - } catch { - print("Failed to decode data") - throw AppError.internalError(.jsonParsingError, message: "Failed to decode data") - } + let sessionsData = try handleDecodingErrors({ + try JSONDecoder().decode(GetUserSessionsData.self, from: responseData) + }) + print("SUCCESS - \(route.label)") + return sessionsData } // IMPORTANT: only use this function to pass in dates of format: diff --git a/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift b/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift index 6df42958..27c18782 100644 --- a/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift +++ b/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift @@ -11,7 +11,7 @@ import SwiftUI final class MenteeProfileViewmodel: ObservableObject { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - @Published var mentee: MenteeInfo? + @Published var mentee: MenteeInfo? = DevelopmentModels.menteeModel @Published var selfView: Bool? func fetchMenteeInfo(userID: String) async throws { diff --git a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift index 3bfacf61..f04e142a 100644 --- a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift +++ b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift @@ -11,89 +11,24 @@ import SwiftUI final class SessionDetailViewModel: ObservableObject { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - @Published var session = Session( - preSessionID: "abc123", - menteePostSessionID: "xyz789", - mentorPostSessionID: "zyx987", - mentor: MentorGetData( - message: "Hello", - mentor: MentorInfo( - id: "123", - name: "Timby Twolf", - imageId: "34709134", - about: "I love chocolate", - calendlyLink: "asdasd", - zoomLink: "zoom.com", - graduationYear: 2016, - college: "UCSD", - major: "CS", - minor: "Business", - career: "SWE", - topicsOfExpertise: ["CS", "AP", "Hi"] - ) - ), - mentee: MenteeGetData( - message: "Hello", - mentee: MenteeInfo( - id: "u123", - name: "Timby Twolf", - imageId: "23462", - about: "I love caramel", - grade: 10, - topicsOfInterest: ["AP", "CS"], - careerInterests: ["SWE"] - ) - ), - day: "Monday", - date: "January 23, 2023", - startTime: "9:00", - endTime: "10:00" - ) + @Published var session: SessionModel? = DevelopmentModels.sessionModel + @Published var formIsComplete: Bool = false @Published var sessionCompleted: Bool = false @Published var isLoading: Bool = true - func loadSession(sessionID: String) async throws { - guard let sessionData = try? await SessionService().getSessionWithID(sessionID: sessionID) else { - print("Error getting session info") - return - } - + func fetchSession(sessionId: String) async throws { DispatchQueue.main.async { - self.sessionCompleted = sessionData.session.hasPassed + self.isLoading = true } - if !sessionData.session.hasPassed { - self.formIsComplete = sessionData.session.preSessionCompleted - } else { - if self.currentUser.role == UserRole.mentor { - self.formIsComplete = sessionData.session.postSessionMentorCompleted - } else { - self.formIsComplete = sessionData.session.postSessionMenteeCompleted + do { + let sessionData = try await SessionService.shared.getSessionWithId(sessionId: sessionId); + DispatchQueue.main.async { + self.session = sessionData.session + self.isLoading = false } + } catch { + print("ERROR SessionDetailViewModel.fetchSession: \(error)") } - - self.session.day = sessionData.session.day - var startDate = SessionService().convertDate(date: sessionData.session.startTime) - var endDate = SessionService().convertDate(date: sessionData.session.endTime) - self.session.date = startDate[1] + "/" + startDate[2] + "/" + startDate[0] - self.session.startTime = startDate[3] + ":" + startDate[4] - self.session.endTime = endDate[3] + ":" + endDate[4] - - self.session.preSessionID = sessionData.session.preSession - self.session.menteePostSessionID = sessionData.session.postSessionMentee ?? "" - self.session.mentorPostSessionID = sessionData.session.postSessionMentor ?? "" - - guard let mentorData = try? await UserService().getMentor(userID: sessionData.session.mentorId) else { - print("Error getting mentor info") - return - } - self.session.mentor = mentorData - - guard let menteeData = try? await UserService().getMentee(userID: sessionData.session.menteeId) else { - print("Error getting mentee info") - return - } - self.session.mentee = menteeData - self.isLoading = false } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift b/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift index fba4d2b8..f565bbf0 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift @@ -13,7 +13,7 @@ struct PostSessionScreenHeaderModifier: ViewModifier { VStack { NavigationHeaderComponent( backText: "XXX", - backDestination: MentorSessionDetailsPage(), + backDestination: Text("TODO Blank"), title: "Post-session Notes", purple: false ) diff --git a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift index bae9d6db..1f3751e6 100644 --- a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift @@ -40,7 +40,7 @@ struct SessionConfirmationScreen: View { Spacer() NavigationLink( - destination: MentorSessionDetailsPage().navigationBarHidden(true), + destination: Text("TODO Blank").navigationBarHidden(true), label: { HStack { Text(text[2]) diff --git a/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift b/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift deleted file mode 100644 index df8e11df..00000000 --- a/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift +++ /dev/null @@ -1,272 +0,0 @@ -// -// MenteeSessionsDetailsPage.swift -// ALUM -// -// Created by Neelam Gurnani on 4/13/23. -// - -import SwiftUI - -struct MenteeSessionDetailsHeaderModifier: ViewModifier { - @State var date: String = "" - @State var mentor: String = "" - - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Session with \(mentor)", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyMenteeSessionDetailsHeaderModifier(date: String, mentor: String) -> some View { - self.modifier(MenteeSessionDetailsHeaderModifier(date: date, mentor: mentor)) - } -} - -struct MenteeSessionsDetailsPage: View { - @StateObject private var viewModel = SessionDetailViewModel() - - var body: some View { - Group { - if !viewModel.isLoading { - GeometryReader { grr in - VStack { - ScrollView { - content - .padding(.horizontal, 16) - } - .frame(minHeight: grr.size.height-120) - - NavigationFooter(page: "Home") - } - .applyMenteeSessionDetailsHeaderModifier( - date: viewModel.session.date, - mentor: viewModel.session.mentor.mentor.name) - .edgesIgnoringSafeArea(.bottom) - } - } else { - ProgressView() - } - } - .onAppear { - Task { - do { - var sessionsArray: [UserSessionInfo] = try await SessionService().getSessionsByUser().sessions - - try await viewModel.loadSession(sessionID: sessionsArray[0].id) - } catch { - print(error) - } - } - } - - } - - var content: some View { - VStack { - Group { - HStack { - Text("Mentor") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.top, 28) - .padding(.bottom, 20) - - NavigationLink(destination: MentorProfileScreen(uID: viewModel.session.mentor.mentor.id)) { - MentorCard(isEmpty: true, uID: viewModel.session.mentor.mentor.id) - .padding(.bottom, 28) - } - } - - Group { - HStack { - Text("Date & Time") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.day + ", " + viewModel.session.date) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.startTime + " - " + viewModel.session.endTime) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 20) - } - - if !viewModel.sessionCompleted { - /* - Button { - - } label: { - Text("Reschedule Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 20) - */ - - Group { - HStack { - Text("Location") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.mentor.mentor.zoomLink) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Dark Blue")) - - Spacer() - } - .padding(.bottom, 20) - } - - Group { - HStack { - Text("Pre-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Pre") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PreSessionView( - notesID: viewModel.session.preSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPreSessionNotesPage( - notesID: viewModel.session.preSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - - Button { - - } label: { - Text("Cancel Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("FunctionalError")) - } - .buttonStyle(OutlinedButtonStyle()) - .border(Color("FunctionalError")) - .cornerRadius(8.0) - } else { - Group { - HStack { - Text("Post-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Post") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PostSessionView( - notesID: viewModel.session.menteePostSessionID, - otherNotesID: viewModel.session.mentorPostSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPostSessionNotesPage( - notesID: viewModel.session.menteePostSessionID, - otherNotesID: viewModel.session.mentorPostSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - } - } - } -} - -struct MenteeSessionsDetailsPage_Previews: PreviewProvider { - static var previews: some View { - MenteeSessionsDetailsPage() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift b/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift deleted file mode 100644 index 493eb756..00000000 --- a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// MentorSessionDetailsPage.swift -// ALUM -// -// Created by Neelam Gurnani on 4/13/23. -// - -import SwiftUI - -struct MentorSessionDetailsHeaderModifier: ViewModifier { - @State var date: String = "" - @State var mentee: String = "" - - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Session with \(mentee)", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyMentorSessionDetailsHeaderModifier(date: String, mentee: String) -> some View { - self.modifier(MentorSessionDetailsHeaderModifier(date: date, mentee: mentee)) - } -} - -struct MentorSessionDetailsPage: View { - @StateObject private var viewModel = SessionDetailViewModel() - - var body: some View { - Group { - if !viewModel.isLoading { - NavigationView { - GeometryReader { grr in - VStack { - ScrollView { - content - .padding(.horizontal, 16) - } - .frame(minHeight: grr.size.height-120) - } - .applyMentorSessionDetailsHeaderModifier( - date: viewModel.session.date, - mentee: viewModel.session.mentee.mentee.name) - .edgesIgnoringSafeArea(.bottom) - } - } - } else { - ProgressView() - } - } - .onAppear { - Task { - do { - var sessionsArray: [UserSessionInfo] = try await SessionService().getSessionsByUser().sessions - - try await viewModel.loadSession(sessionID: sessionsArray[0].id) - } catch { - print(error) - } - } - } - } - - var content: some View { - VStack { - Group { - HStack { - Text("Mentee") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.top, 28) - .padding(.bottom, 20) - - NavigationLink(destination: MenteeProfileScreen(uID: viewModel.session.mentee.mentee.id)) { - HorizontalMenteeCard( - name: viewModel.session.mentee.mentee.name, - grade: viewModel.session.mentee.mentee.grade, - school: "NHS", - isEmpty: true - ) - .padding(.bottom, 28) - } - } - - Group { - HStack { - Text("Date & Time") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.day + ", " + viewModel.session.date) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.startTime + " - " + viewModel.session.endTime) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 20) - } - - if !viewModel.sessionCompleted { - /* - Button { - - } label: { - Text("Reschedule Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 20) - */ - - Group { - HStack { - Text("Location") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.mentor.mentor.zoomLink) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Dark Blue")) - - Spacer() - } - .padding(.bottom, 20) - } - - Group { - HStack { - Text("Pre-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - NavigationLink { - ViewPreSessionNotesPage(notesID: viewModel.session.preSessionID) - } label: { - Text("View Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - - Button { - - } label: { - Text("Cancel Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("FunctionalError")) - } - .buttonStyle(OutlinedButtonStyle()) - .border(Color("FunctionalError")) - .cornerRadius(8.0) - } else { - Group { - HStack { - Text("Post-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Post") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PostSessionView( - notesID: viewModel.session.mentorPostSessionID, - otherNotesID: viewModel.session.menteePostSessionID, - otherName: viewModel.session.mentee.mentee.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPostSessionNotesPage( - notesID: viewModel.session.mentorPostSessionID, - otherNotesID: viewModel.session.menteePostSessionID, - otherName: viewModel.session.mentee.mentee.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - } - } - } -} - -struct MentorSessionDetailsPage_Previews: PreviewProvider { - static var previews: some View { - MentorSessionDetailsPage() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift new file mode 100644 index 00000000..9c2cbf43 --- /dev/null +++ b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift @@ -0,0 +1,415 @@ +// +// SessionDetailsScreen.swift +// ALUM +// +// Created by Aman Aggarwal on 5/22/23. +// + +import SwiftUI + +let isMVP: Bool = true + +struct SessionDetailsScreen: View { + var sessionId: String + var hideBookSessionButtonOverride: Bool + @StateObject private var viewModel = SessionDetailViewModel() + // Shows/Hides the calendly web view + @State public var showCalendlyWebView = false + + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + + var body: some View { + return navigationBarConfig + } + + var loadingAbstraction: some View { + Group { + if viewModel.isLoading || viewModel.session == nil { + LoadingView(text: "SessionDetailsScreen \(sessionId)") + .onAppear(perform: { + Task { + do { + try await viewModel.fetchSession(sessionId: sessionId) + } catch { + print("Error") + } + } + }) + } else { + screenContent + } + } + } + + var navigationBarConfig: some View { + let session = viewModel.session! + var otherName: String + + switch currentUser.role { + case .mentee: + otherName = session.mentorName + case .mentor: + otherName = session.menteeName + case .none: + otherName = "" + // TODO Internal error + } + + return screenContent + .customNavBarItems( + title: "\(session.dateShortHandString) Session with \(otherName)", + isPurple: false, + backButtonHidden: false + ) + .background(ALUMColor.beige.color) + } + + var screenContent: some View { + let session = viewModel.session! + + return VStack(alignment: .leading) { + if currentUser.role == .mentee { + menteeView + } else { + mentorView + } + if !session.hasPassed { + Button { + + } label: { + ALUMText(text: "Cancel Session", textColor: ALUMColor.red) + } + .buttonStyle(OutlinedButtonStyle()) + .border(ALUMColor.red.color) + .cornerRadius(8.0) + } + Spacer() + } + .padding(.horizontal, 16) + .padding(.top, 28) + + } + + + + // Section which displays the date and time + var dateTimeDisplaySection: some View { + let session = viewModel.session! + + return VStack(alignment: .leading) { + HStack { + ALUMText(text: "Date & Time", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + HStack { + ALUMText(text: session.fullDateString, textColor: ALUMColor.black) + Spacer() + } + .padding(.bottom, 5) + + HStack { + ALUMText(text: "\(session.startTimeString) - \(session.endTimeString)", textColor: ALUMColor.black) + Spacer() + } + } + .padding(.bottom, 20) + } + + var bookSessionButton: some View { + let session = viewModel.session! + let buttonDisabled: Bool = hideBookSessionButtonOverride || !session.postSessionMenteeCompleted + + + return Button { + showCalendlyWebView = true + } label: { + ALUMText(text: "Book Session via Calendly", textColor: ALUMColor.white) + } + .disabled(buttonDisabled) + .sheet(isPresented: $showCalendlyWebView) { + CalendlyView() + } + .buttonStyle(FilledInButtonStyle(disabled: buttonDisabled)) + .padding(.bottom, 26) + } + + + + var locationSectionForAny: some View { + let session = viewModel.session! + + return Group { + HStack { + ALUMText(text: "Location", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + + HStack { + ALUMText(text: session.location, textColor: ALUMColor.primaryBlue) + Spacer() + } + .padding(.bottom, 20) + } + } + + var postSessionNotesSectionForAny: some View { + let session = viewModel.session! + var formIsComplete: Bool, editableNoteId: String, otherNoteId: String, otherName: String + + if session.postSessionMentee == nil || session.postSessionMentor == nil { + print("Attempting to display session view but no post session notes IDs present ") + // TODO internal error + } + + switch currentUser.role { + case .mentee: + formIsComplete = session.postSessionMenteeCompleted + editableNoteId = session.postSessionMentee! + otherNoteId = session.postSessionMentor! + otherName = session.mentorName + case .mentor: + formIsComplete = session.postSessionMenteeCompleted + editableNoteId = session.postSessionMentor! + otherNoteId = session.postSessionMentee! + otherName = session.menteeName + case .none: + formIsComplete = true + editableNoteId = "" + otherNoteId = "" + otherName = "" + // TODO Internal error + } + + return Group { + HStack { + ALUMText(text: "Post-Session Form", textColor: ALUMColor.gray4) + + Spacer() + } + .padding(.bottom, formIsComplete ? 20 : 5) + + if !formIsComplete { + HStack { + FormIncompleteComponent(type: "Post") + Spacer() + } + .padding(.bottom, 22) + } + + if !formIsComplete { + NavigationLink { + PostSessionView( + notesID: editableNoteId, + otherNotesID: otherNoteId, + otherName: otherName, + date: session.dateShortHandString, + time: session.startTimeString + ) + } label: { + ALUMText(text: "Complete Post-Session Notes", textColor: ALUMColor.white) + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) + } else { + NavigationLink { + ViewPostSessionNotesPage( + notesID: editableNoteId, + otherNotesID: otherNoteId, + otherName: otherName, + date: session.dateShortHandString, + time: session.startTimeString + ) + } label: { + ALUMText(text: "View Post-Session Notes", textColor: ALUMColor.white) + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) + } + + NavigationLink { + ViewPreSessionNotesPage( + allowEditing: false, + notesID: session.preSession, + otherName: otherName + ) + } label: { + ALUMText(text: "View Pre-Session Notes") + } + .buttonStyle(OutlinedButtonStyle()) + .padding(.bottom, 5) + } + } +} + +// Sections only for mentor +extension SessionDetailsScreen { + var mentorView: some View { + let session = viewModel.session! + + return Group { + HStack { + ALUMText(text: "Mentee", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + + NavigationLink(destination: MenteeProfileScreen(uID: session.menteeId)) { + HorizontalMenteeCard( + menteeId: session.menteeId, + isEmpty: true + ) + .padding(.bottom, 28) + } + dateTimeDisplaySection + if session.hasPassed { + afterEventSectionMentor + } else { + beforeEventSectionMentor + } + } + } + + var beforeEventSectionMentor: some View { + return Group { + locationSectionForAny + preSessionNotesSectionForMentor + } + } + + var afterEventSectionMentor: some View { + return VStack { + postSessionNotesSectionForAny + } + } + + var preSessionNotesSectionForMentor: some View { + // Mentor can view mentee's pre-session notes + let session = viewModel.session! + + return Group { + HStack { + ALUMText(text: "Pre-Session Form", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 20) + + NavigationLink { + ViewPreSessionNotesPage(allowEditing: false, notesID: session.preSession, otherName: session.menteeName) + } label: { + ALUMText(text: "View Pre-Session Form", textColor: ALUMColor.white) + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) + } + } +} + +// Sections only for mentee +extension SessionDetailsScreen { + var menteeView: some View { + let session = viewModel.session! + + return Group { + bookSessionButton + + HStack { + ALUMText(text: "Mentor", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + + NavigationLink(destination: MentorProfileScreen(uID: session.mentorId)) { + MentorCard(isEmpty: true, uID: session.mentorId) + .padding(.bottom, 28) + } + dateTimeDisplaySection + + if session.hasPassed { + afterEventSectionMentee + } else { + beforeEventSectionMentee + } + } + + } + + var afterEventSectionMentee: some View { + return VStack { + postSessionNotesSectionForAny + } + } + + var beforeEventSectionMentee: some View { + return Group { + locationSectionForAny + Button { + print("TODO Reschedule Session not implemented") + } label: { + ALUMText(text: "Reschedule Session") + } + .buttonStyle(OutlinedButtonStyle()) + .padding(.bottom, 20) + preSessionNotesSectionForMentee + } + } + + var preSessionNotesSectionForMentee: some View { + let session = viewModel.session! + + return Group { + HStack { + ALUMText(text: "Pre-Session Form", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, viewModel.formIsComplete ? 20 : 5) + + if !session.preSessionCompleted { + HStack { + FormIncompleteComponent(type: "Pre") + Spacer() + } + .padding(.bottom, 22) + } + + if !session.preSessionCompleted { + NavigationLink { + PreSessionView( + notesID: session.preSession, + otherName: session.mentorName, + date: session.dateShortHandString, + time: session.startTimeString + ) + } label: { + ALUMText(text: "Complete Pre-Session Notes", textColor: ALUMColor.white) + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) + } else { + NavigationLink { + ViewPreSessionNotesPage( + allowEditing: true, + notesID: session.preSession, + otherName: session.mentorName, + date: session.dateShortHandString, + time: session.startTimeString + ) + } label: { + ALUMText(text: "View Pre-Session Notes", textColor: ALUMColor.white) + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) + } + } + } +} +struct SessionDetailsScreen_Previews: PreviewProvider { + static var previews: some View { + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentee, status: "paired") + + return CustomNavView { + SessionDetailsScreen(sessionId: "6464276b6f05d9703f069760", hideBookSessionButtonOverride: false) + } + } +} diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift index cb835d77..67b2b2b3 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift @@ -31,14 +31,14 @@ extension View { } struct ViewPreSessionNotesPage: View { + var allowEditing: Bool + var notesID: String + var otherName: String + var date: String = "" + var time: String = "" + @StateObject var viewModel = QuestionViewModel() - @State var notesID: String = "" - - @State var otherName: String = "" - @State var date: String = "" - @State var time: String = "" - var body: some View { Group { if !viewModel.isLoading { @@ -47,7 +47,7 @@ struct ViewPreSessionNotesPage: View { content } - if viewModel.currentUser.role == UserRole.mentee { + if allowEditing { footer .padding(.horizontal, 16) .padding(.top, 32) @@ -155,6 +155,6 @@ struct ViewPreSessionNotesPage: View { struct ViewPreSessionNotesPage_Previews: PreviewProvider { static var previews: some View { - ViewPreSessionNotesPage() + ViewPreSessionNotesPage(allowEditing: false, notesID: "", otherName: "mentor") } } diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 78c63ef8..9e9a6315 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -13,6 +13,8 @@ import { CreateSessionRequestBodyCake } from "../types/cakes"; import { InternalError, ServiceError } from "../errors"; import { getCalendlyEventDate } from "../services/calendly"; import { getMentorId } from "../services/user"; +import {formatDateTimeRange} from "../services/session"; + /** * This is a post route to create a new session. @@ -98,17 +100,9 @@ router.get( async (req: Request, res: Response, next: NextFunction) => { try { const sessionId = req.params.sessionId; - const dayNames = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ]; const dateNow = new Date(); + // Find session document if (!mongoose.Types.ObjectId.isValid(sessionId)) { throw ServiceError.INVALID_MONGO_ID; } @@ -117,6 +111,19 @@ router.get( if (!session) { throw ServiceError.SESSION_WAS_NOT_FOUND; } + + // Find mentor document for the location + const mentor = await Mentor.findById(session.mentorId); + if (!mentor) { + throw ServiceError.MENTOR_WAS_NOT_FOUND; + } + + // Find mentee document for the location + const mentee = await Mentee.findById(session.menteeId); + if (!mentee) { + throw ServiceError.MENTEE_WAS_NOT_FOUND; + } + const { preSession, postSessionMentee, @@ -129,7 +136,10 @@ router.get( postSessionMenteeCompleted, postSessionMentorCompleted, } = session; + const [fullDateString, dateShortHandString, startTimeString, endTimeString]= formatDateTimeRange(startTime, endTime); + const hasPassed = dateNow.getTime() - endTime.getTime() > 0; + return res.status(200).send({ message: `Here is session ${sessionId}`, session: { @@ -138,13 +148,17 @@ router.get( postSessionMentor, menteeId, mentorId, - startTime, - endTime, - day: dayNames[startTime.getDay()], + menteeName: mentee.name, + mentorName: mentor.name, + fullDateString, + dateShortHandString, + startTimeString, + endTimeString, preSessionCompleted, postSessionMenteeCompleted, postSessionMentorCompleted, hasPassed, + location: mentor.location }, }); } catch (e) { diff --git a/backend/src/services/session.ts b/backend/src/services/session.ts new file mode 100644 index 00000000..5dcf74df --- /dev/null +++ b/backend/src/services/session.ts @@ -0,0 +1,49 @@ +/** + * Function takes in startTime and endTime date objects and formats it to give: + * [ + * "Thursday, April 27, 2023" OR "Thursday, April 27, 2023 - Friday, April 28, 2023", + * "5/22" + * "2:00 PM" + * "2:30 PM" + * ] + */ +export function formatDateTimeRange(startTime: Date, endTime: Date): [string, string, string, string] { + const dateOptions: Intl.DateTimeFormatOptions = { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }; + + const timeOptions: Intl.DateTimeFormatOptions = { + hour: "numeric", + minute: "numeric", + hour12: true, + }; + + const dateShortHandOptions: Intl.DateTimeFormatOptions = { + month: "numeric", + day: "numeric", + }; + + const startDateString = startTime.toLocaleDateString("en-US", dateOptions); + const startTimeString = startTime.toLocaleTimeString("en-US", timeOptions); + const endDateString = endTime.toLocaleDateString("en-US", dateOptions); + const endTimeString = endTime.toLocaleTimeString("en-US", timeOptions); + const dateShortHandString = startTime.toLocaleDateString("en-US", dateShortHandOptions); + + let fullDateString + if (startDateString === endDateString) { + // Thursday, April 27, 2023 at 2:00 PM - 2:30 PM + fullDateString = startDateString + } else { + fullDateString = endDateString + } + + return [fullDateString, dateShortHandString, startTimeString, endTimeString] + // Thursday, April 27, 2023 at 2:00 PM - Friday, April 28, 2023 at 2:30 PM +} + +const startDate = new Date(); +const endDate = new Date(); +console.log(formatDateTimeRange(startDate, endDate)) \ No newline at end of file From 9275c0fb5bfa479d1ab363e0fdef83c4b63850c5 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 03:12:33 -0700 Subject: [PATCH 11/33] create a home screen for no sessions + add/integrate a GET self route used when app boots up --- ALUM/ALUM.xcodeproj/project.pbxproj | 4 + ALUM/ALUM/Models/CurrentUserModel.swift | 54 +++++++++++-- ALUM/ALUM/Services/APIConfig.swift | 12 ++- .../FirebaseAuthenticationService.swift | 2 +- ALUM/ALUM/Services/UserService.swift | 20 +++++ ALUM/ALUM/Views/MenteeProfileScreen.swift | 2 +- ALUM/ALUM/Views/MentorProfileScreen.swift | 2 +- ALUM/ALUM/Views/Routers/LoggedInRouter.swift | 64 +++++++++------ .../SessionDetailsScreen.swift | 28 ++++--- backend/src/errors/internal.ts | 4 + backend/src/errors/service.ts | 6 +- backend/src/models/mentee.ts | 6 +- backend/src/models/mentor.ts | 7 +- backend/src/models/session.ts | 2 +- backend/src/routes/user.ts | 80 +++++++++++++++++++ backend/src/services/session.ts | 29 +++++++ backend/src/services/user.ts | 1 + backend/src/types/user.ts | 2 +- 18 files changed, 268 insertions(+), 57 deletions(-) diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 25e4e523..41679e53 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0723BB862A1C995700911948 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB852A1C995700911948 /* HomeScreen.swift */; }; 0726C9EF29DC4B120042A486 /* CustomErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0726C9EE29DC4B120042A486 /* CustomErrors.swift */; }; 0748208F29712921004AF547 /* ALUMApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748208E29712921004AF547 /* ALUMApp.swift */; }; 0748209129712921004AF547 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748209029712921004AF547 /* ContentView.swift */; }; @@ -126,6 +127,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 0723BB852A1C995700911948 /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; 0726C9EE29DC4B120042A486 /* CustomErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomErrors.swift; sourceTree = ""; }; 0748208B29712921004AF547 /* ALUM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ALUM.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0748208E29712921004AF547 /* ALUMApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALUMApp.swift; sourceTree = ""; }; @@ -410,6 +412,7 @@ 97992B5D29A6E7E200701CC7 /* SignUpPage */, 0748209029712921004AF547 /* ContentView.swift */, 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */, + 0723BB852A1C995700911948 /* HomeScreen.swift */, ); path = Views; sourceTree = ""; @@ -741,6 +744,7 @@ 97A2FE8C2989C20900405FD6 /* LoginScreen.swift in Sources */, 07A565C82A1C3806008C96BC /* SessionDetailsScreen.swift in Sources */, 80210ED429C3DBD9008B912A /* FirebaseAuthenticationService.swift in Sources */, + 0723BB862A1C995700911948 /* HomeScreen.swift in Sources */, 0793B4212A19FA7200AF78C8 /* CustomNavBarView.swift in Sources */, 80F5388C29B0252F00FB5E66 /* UserService.swift in Sources */, 80274FF5299DB03900CCB9D0 /* LoginViewModel.swift in Sources */, diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index cb62b957..a5a269f5 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -22,6 +22,10 @@ class CurrentUserModel: ObservableObject { @Published var isLoggedIn: Bool @Published var status: String? @Published var showTabBar: Bool + + @Published var upcomingSessionId: String? + @Published var pairedMentorId: String? + @Published var pairedMenteeId: String? init() { self.isLoading = true @@ -35,13 +39,12 @@ class CurrentUserModel: ObservableObject { /// Since async operations are involved, this function will limit updating the current /// user without using the DispatchQueue logic. /// Not using DispatchQueue can casue race conditions which can crash our app - func setCurrentUser(isLoading: Bool, isLoggedIn: Bool, uid: String?, role: UserRole?, status: String?) { + func setCurrentUser(isLoading: Bool, isLoggedIn: Bool, uid: String?, role: UserRole?) { DispatchQueue.main.async { self.isLoading = isLoading self.isLoggedIn = isLoggedIn self.uid = uid self.role = role - self.status = status } } @@ -53,7 +56,7 @@ class CurrentUserModel: ObservableObject { } try await self.setFromFirebaseUser(user: user) } catch { - self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil, status: nil) + self.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) } } @@ -79,9 +82,50 @@ class CurrentUserModel: ObservableObject { message: "Expected user role to be mentor OR mentee but found - \(role)" ) } - await self.setCurrentUser(isLoading: false, isLoggedIn: true, uid: user.uid, role: roleEnum, - status: try getStatus(userID: user.uid, roleEnum: roleEnum)) + self.setCurrentUser(isLoading: true, isLoggedIn: true, uid: user.uid, role: roleEnum) + try await self.fetchUserInfoFromServer(userId: user.uid, role: roleEnum) + print("loading done", roleEnum) + DispatchQueue.main.async { + self.isLoading = false + } + } + + func fetchUserInfoFromServer(userId: String, role: UserRole) async throws { + let userData = try await UserService.shared.getSelf() + let userStatus = userData.status + + DispatchQueue.main.async { + self.status = userStatus + } + + if userStatus != "paired" { + print("early return") + return + } + + if self.role == .mentee { + guard let userPairedMentorId = userData.pairedMentorId else { + throw AppError.internalError(.invalidResponse, message: "Expected mentee to have a paired mentor Id") + } + print("userPairedMentorId - \(userPairedMentorId)") + DispatchQueue.main.async { + self.pairedMentorId = userPairedMentorId + } + } else if self.role == .mentor { + guard let userPairedMenteeId = userData.pairedMenteeId else { + throw AppError.internalError(.invalidResponse, message: "Expected mentor to have a paired mentee Id") + } + print("userPairedMenteeId - \(userPairedMenteeId)") + DispatchQueue.main.async { + self.pairedMenteeId = userPairedMenteeId + } + } + + DispatchQueue.main.async { + self.upcomingSessionId = userData.upcomingSessionId + } } + func getStatus(userID: String, roleEnum: UserRole) async throws -> String { let userStatus: String switch roleEnum { diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 488fda8c..3b799b16 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -9,6 +9,7 @@ import Foundation let baseURL: String = "http://localhost:3000" struct URLString { + static let user = "\(baseURL)/user" static let mentor = "\(baseURL)/mentor" static let mentee = "\(baseURL)/mentee" static let notes = "\(baseURL)/notes" @@ -17,6 +18,7 @@ struct URLString { } enum APIRoute { + case getSelf case getMentor(userId: String) case getMentee(userId: String) case postMentor @@ -32,6 +34,8 @@ enum APIRoute { var url: String { switch self { + case .getSelf: + return [URLString.user, "me"].joined(separator: "/") case .getMentor(let userId): return [URLString.mentor, userId].joined(separator: "/") case .getMentee(let userId): @@ -57,7 +61,7 @@ enum APIRoute { var method: String { switch self { - case .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly: + case .getSelf, .getMentee, .getMentor, .getNote, .getSession, .getSessions, .getCalendly: return "GET" case .postMentor, .postMentee, .postSession: return "POST" @@ -68,7 +72,7 @@ enum APIRoute { var requireAuth: Bool { switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .postSession, .getCalendly: + case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .postSession, .getCalendly: return true case .postMentee, .postMentor: return false @@ -89,7 +93,7 @@ enum APIRoute { var successCode: Int { switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: + case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: return 200 // 200 Ok case .postMentor, .postMentee, .postSession: return 201 // 201 Created @@ -101,7 +105,7 @@ enum APIRoute { let errorMap: [Int: AppError] switch self { - case .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: + case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .getCalendly: errorMap = [ 401: AppError.actionable(.authenticationError, message: labeledMessage), 400: AppError.internalError(.invalidRequest, message: labeledMessage), diff --git a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift index 6a32ced0..533ff89d 100644 --- a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift +++ b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift @@ -21,7 +21,7 @@ final class FirebaseAuthenticationService: ObservableObject { func logout() { do { try Auth.auth().signOut() - self.currentUser.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil, status: nil) + self.currentUser.setCurrentUser(isLoading: false, isLoggedIn: false, uid: nil, role: nil) print("logged out successfuly") } catch let error { print("error occured in FirebaseAuthenticationService - ", error.localizedDescription) diff --git a/ALUM/ALUM/Services/UserService.swift b/ALUM/ALUM/Services/UserService.swift index a6cc4365..a0ee2bb0 100644 --- a/ALUM/ALUM/Services/UserService.swift +++ b/ALUM/ALUM/Services/UserService.swift @@ -33,6 +33,13 @@ struct MentorPostData: Codable { var personalAccessToken: String } +struct SelfGetData: Decodable { + var status: String + var upcomingSessionId: String? + var pairedMentorId: String? + var pairedMenteeId: String? +} + struct MenteeGetData: Decodable { var message: String var mentee: MenteeInfo @@ -48,6 +55,19 @@ struct MentorGetData: Decodable { class UserService { static let shared = UserService() + func getSelf() async throws -> SelfGetData { + let route = APIRoute.getSelf + var request = try await route.createURLRequest() + let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) + + let userData = try handleDecodingErrors({ + try JSONDecoder().decode(SelfGetData.self, from: responseData) + }) + + print("SUCCESS - \(route.label) - \(userData.pairedMenteeId) - \(userData.pairedMentorId)") + return userData + } + func createMentee(data: MenteePostData) async throws { let route = APIRoute.postMentee var request = try await route.createURLRequest() diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index b5489405..dfd37220 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -157,7 +157,7 @@ extension MenteeProfileScreen { struct MenteeProfileView_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentor, status: "paired") + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentor) return CustomNavView { MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") .onAppear { diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 862953d1..86868150 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -207,7 +207,7 @@ extension MentorProfileScreen { struct MentorProfileScreen_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor, status: "paired") + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor) return CustomNavView { MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") .onAppear { diff --git a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift index 038db4f8..b09b180f 100644 --- a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -7,7 +7,7 @@ import SwiftUI -struct ProfileRouter: View { +struct ProfileTabRouter: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared var body: some View { @@ -26,6 +26,20 @@ struct ProfileRouter: View { } } +struct HomeTabRouter: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + + var body: some View { + CustomNavView { + if currentUser.upcomingSessionId == nil { + HomeScreen() + } else { + SessionDetailsScreen(sessionId: currentUser.upcomingSessionId!) + } + } + } +} + struct PlaceHolderHomeScreen: View { var body: some View { NavigationView { @@ -53,28 +67,30 @@ struct LoggedInRouter: View { ] var body: some View { - if currentUser.status == "under review" { - if currentUser.role == .mentee { - LoginReviewPage(text: - ["Application is under review", - "It usually takes 3-5 days to process your application as a mentee."]) - } else if currentUser.role == .mentor { - LoginReviewPage(text: - ["Application is under review", - "It usually takes 3-5 days to process your application as a mentor."]) - } - } else if currentUser.status == "approved" { - if currentUser.role == .mentee { - LoginReviewPage(text: - ["Matching you with a mentor", - "We are looking for a perfect mentor for you. Please allow us some time!"]) - } else if currentUser.role == .mentor { - LoginReviewPage(text: - ["Matching you with a mentee", - "We are looking for a perfect mentee for you. Please allow us some time!"]) + Group { + if currentUser.status == "under review" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentee."]) + } else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentor."]) + } + } else if currentUser.status == "approved" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Matching you with a mentor", + "We are looking for a perfect mentor for you. Please allow us some time!"]) + } else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Matching you with a mentee", + "We are looking for a perfect mentee for you. Please allow us some time!"]) + } + } else { + pairedUserView } - } else { - pairedUserView } } @@ -83,12 +99,12 @@ struct LoggedInRouter: View { VStack(spacing: 0) { switch selection { case 0: - PlaceHolderHomeScreen() + HomeTabRouter() .onAppear(perform: { currentUser.showTabBar = true }) case 1: - ProfileRouter() + ProfileTabRouter() .onAppear(perform: { currentUser.showTabBar = true }) diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift index 9c2cbf43..08b36bc3 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift @@ -11,7 +11,7 @@ let isMVP: Bool = true struct SessionDetailsScreen: View { var sessionId: String - var hideBookSessionButtonOverride: Bool + @StateObject private var viewModel = SessionDetailViewModel() // Shows/Hides the calendly web view @State public var showCalendlyWebView = false @@ -19,7 +19,7 @@ struct SessionDetailsScreen: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared var body: some View { - return navigationBarConfig + return loadingAbstraction } var loadingAbstraction: some View { @@ -36,7 +36,7 @@ struct SessionDetailsScreen: View { } }) } else { - screenContent + navigationBarConfig } } } @@ -55,13 +55,15 @@ struct SessionDetailsScreen: View { // TODO Internal error } - return screenContent - .customNavBarItems( - title: "\(session.dateShortHandString) Session with \(otherName)", - isPurple: false, - backButtonHidden: false - ) - .background(ALUMColor.beige.color) + return ScrollView { + screenContent + } + .customNavBarItems( + title: "\(session.dateShortHandString) Session with \(otherName)", + isPurple: false, + backButtonHidden: false + ) + .background(ALUMColor.beige.color) } var screenContent: some View { @@ -118,7 +120,7 @@ struct SessionDetailsScreen: View { var bookSessionButton: some View { let session = viewModel.session! - let buttonDisabled: Bool = hideBookSessionButtonOverride || !session.postSessionMenteeCompleted + let buttonDisabled: Bool = !session.postSessionMenteeCompleted return Button { @@ -406,10 +408,10 @@ extension SessionDetailsScreen { } struct SessionDetailsScreen_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentee, status: "paired") + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentee) return CustomNavView { - SessionDetailsScreen(sessionId: "6464276b6f05d9703f069760", hideBookSessionButtonOverride: false) + SessionDetailsScreen(sessionId: "6464276b6f05d9703f069760") } } } diff --git a/backend/src/errors/internal.ts b/backend/src/errors/internal.ts index f8d0450b..5c267ca1 100644 --- a/backend/src/errors/internal.ts +++ b/backend/src/errors/internal.ts @@ -15,6 +15,8 @@ const NO_DEFAULT_IMAGE_ID = "Could not find default image id env variable"; const ERROR_ROLES_NOT_MENTOR_MENTEE_NOT_IMPLEMENTED = "Any roles other than mentor/mentee has not been implemented."; const ERROR_FINDING_PAIR = "There was an error getting the mentee/mentor pairing"; +const ERROR_FINDING_UPCOMING_SESSION = "Error occured while finding some upcoming session" + export class InternalError extends CustomError { static ERROR_GETTING_MENTEE = new InternalError(0, 500, ERROR_GETTING_MENTEE); @@ -39,4 +41,6 @@ export class InternalError extends CustomError { ); static ERROR_FINDING_PAIR = new InternalError(9, 500, ERROR_FINDING_PAIR); + + static ERROR_FINDING_UPCOMING_SESSION = new InternalError(10, 500, ERROR_FINDING_UPCOMING_SESSION); } diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 399b2786..1b09daa4 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -17,7 +17,7 @@ const NOTE_WAS_NOT_SAVED = "Note was not saved"; const SESSION_WAS_NOT_FOUND = "Session was not found"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; - +const INVALID_ROLE_WAS_FOUND = "Allowed user roles for this context is mentor and mentee only" export class ServiceError extends CustomError { static IMAGE_NOT_SAVED = new ServiceError(0, 404, IMAGE_NOT_SAVED); @@ -40,4 +40,8 @@ export class ServiceError extends CustomError { static NOTE_WAS_NOT_FOUND = new ServiceError(9, 404, NOTE_WAS_NOT_FOUND); static NOTE_WAS_NOT_SAVED = new ServiceError(10, 404, NOTE_WAS_NOT_SAVED); + + static INVALID_ROLE_WAS_FOUND = new ServiceError(11, 404, INVALID_ROLE_WAS_FOUND); + + } diff --git a/backend/src/models/mentee.ts b/backend/src/models/mentee.ts index 226efa27..c3ac50ce 100644 --- a/backend/src/models/mentee.ts +++ b/backend/src/models/mentee.ts @@ -1,4 +1,5 @@ import mongoose from "mongoose"; +import { UserStatusType } from "../types"; interface MenteeInterface { name: string; @@ -9,7 +10,7 @@ interface MenteeInterface { careerInterests: string[]; mentorshipGoal: string; pairingId: string; - status: string; + status: UserStatusType; } interface MenteeDoc extends mongoose.Document { @@ -21,7 +22,7 @@ interface MenteeDoc extends mongoose.Document { careerInterests: string[]; mentorshipGoal: string; pairingId: string; - status: string; + status: UserStatusType; } interface MenteeModelInterface extends mongoose.Model { @@ -67,6 +68,7 @@ const MenteeSchema = new mongoose.Schema({ }, status: { type: String, + enum: ["paired", "approved", "under review"], required: true, }, }); diff --git a/backend/src/models/mentor.ts b/backend/src/models/mentor.ts index 89fd9f2f..1f992759 100644 --- a/backend/src/models/mentor.ts +++ b/backend/src/models/mentor.ts @@ -4,7 +4,7 @@ * be stored on firebase */ import mongoose from "mongoose"; - +import { UserStatusType } from "../types"; interface MentorInterface { name: string; imageId: string; @@ -19,7 +19,7 @@ interface MentorInterface { topicsOfExpertise: string[]; mentorMotivation: string; pairingIds: string[]; - status: string; + status: UserStatusType; personalAccessToken: string; location: string; } @@ -38,7 +38,7 @@ interface MentorDoc extends mongoose.Document { topicsOfExpertise: string[]; mentorMotivation: string; pairingIds: string[]; - status: string; + status: UserStatusType; personalAccessToken: string; location: string; } @@ -102,6 +102,7 @@ const mentorSchema = new mongoose.Schema({ ], status: { type: String, + enum: ["paired", "approved", "under review"], required: true, }, personalAccessToken: { diff --git a/backend/src/models/session.ts b/backend/src/models/session.ts index 227a3b00..0cd25129 100644 --- a/backend/src/models/session.ts +++ b/backend/src/models/session.ts @@ -20,7 +20,7 @@ interface SessionInterface { postSessionMenteeCompleted: boolean; } -interface SessionDoc extends mongoose.Document { +export interface SessionDoc extends mongoose.Document { preSession: ObjectId; postSessionMentee: ObjectId; postSessionMentor: ObjectId; diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 60ddb980..6ca5cb02 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -7,6 +7,7 @@ import mongoose from "mongoose"; import { validateReqBodyWithCake } from "../middleware/validation"; import { Mentee, Mentor, Pairing } from "../models"; import { createUser } from "../services/auth"; +import { getMenteeId, getMentorId } from "../services/user"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake, @@ -19,6 +20,7 @@ import { ServiceError } from "../errors/service"; import { verifyAuthToken } from "../middleware/auth"; import { defaultImageID } from "../config"; import { CustomError } from "../errors"; +import { getUpcomingSession } from "../services/session"; const router = express.Router(); @@ -364,4 +366,82 @@ router.get( } ); +/** + * Route to setup mobile app for any logged in user (mentor or mentee) + * + * This route returns the following + * If user is a mentor, + * menteeIds, status, upcomingSessionId + * + * If user is mentee, + * mentorId, status, upcomingSessionId + */ +router.get( + "/user/me", + [verifyAuthToken], + async (req: Request, res: Response, next: NextFunction) => { + try { + const userId = req.body.uid; + const role = req.body.role; + + const getUpcomingSessionPromise = getUpcomingSession(userId, role) + if (role == "mentee") { + // GET mentee document + const mentee = await Mentee.findById(userId); + if (!mentee) { + throw ServiceError.MENTEE_WAS_NOT_FOUND; + } + + if (mentee.status != "paired") { + res.status(200).send({ + status: mentee.status + }) + } + const getPairedMentorIdPromise = getMentorId(mentee.pairingId) + const [sessionId, pairedMentorId] = await Promise.all([getUpcomingSessionPromise, getPairedMentorIdPromise]) + console.log({ + status: mentee.status, + upcomingSessionId: sessionId, + pairedMentorId + }) + res.status(200).send({ + status: mentee.status, + upcomingSessionId: sessionId, + pairedMentorId + }) + } else if (role == "mentor") { + const mentor = await Mentor.findById(userId); + if (!mentor) { + throw ServiceError.MENTOR_WAS_NOT_FOUND; + } + + if (mentor.status != "paired") { + res.status(200).send({ + status: mentor.status + }) + } + + const getMenteeIdsPromises = mentor.pairingIds.map(async (pairingId) => getMenteeId(pairingId)); + + // For MVP, we assume there is only 1 mentee 1 mentor pairing + const getMenteeIdsPromise = getMenteeIdsPromises[0] + + const [sessionId, pairedMenteeId] = await Promise.all([getUpcomingSessionPromise, getMenteeIdsPromise]); + + res.status(200).send({ + status: mentor.status, + upcomingSessionId: sessionId, + pairedMenteeId + }) + } + } catch (e) { + if (e instanceof CustomError) { + next(e); + return; + } + next(InternalError.ERROR_GETTING_MENTEE); + } + } +) + export { router as userRouter }; diff --git a/backend/src/services/session.ts b/backend/src/services/session.ts index 5dcf74df..29b18fd9 100644 --- a/backend/src/services/session.ts +++ b/backend/src/services/session.ts @@ -1,3 +1,32 @@ +import { Types } from 'mongoose'; +import { InternalError, ServiceError } from "../errors"; +import { Session, SessionDoc } from '../models'; + +export async function getUpcomingSession(userId: string, role: 'mentor' | 'mentee'): Promise { + const now = new Date(); + + let matchField: string; + + if (role === 'mentor') { + matchField = 'mentorId'; + } else if (role === 'mentee') { + matchField = 'menteeId'; + } else { + throw ServiceError.INVALID_ROLE_WAS_FOUND + } + + try { + const upcomingSession = await Session.findOne({ + startTime: { $gt: now }, + [matchField]: new Types.ObjectId(userId), + }).sort({ startTime: 1 }); + + return upcomingSession?._id; + } catch (error) { + throw InternalError.ERROR_FINDING_UPCOMING_SESSION + } +} + /** * Function takes in startTime and endTime date objects and formats it to give: * [ diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index 44fb012a..7b90fe16 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -42,4 +42,5 @@ async function getMenteeId(pairingId: string): Promise { return pairing.menteeId; } + export { getMentorId, getMenteeId }; diff --git a/backend/src/types/user.ts b/backend/src/types/user.ts index 56362432..9adbd5a7 100644 --- a/backend/src/types/user.ts +++ b/backend/src/types/user.ts @@ -1,5 +1,5 @@ import { Infer } from "caketype"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake } from "./cakes"; - +export type UserStatusType = "paired" | "under review" | "approved" export type CreateMenteeRequestBodyType = Infer; export type CreateMentorRequestBodyType = Infer; From e2ccfb867a4751b0bba926964ceb003ba287ee7d Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 16:05:38 -0700 Subject: [PATCH 12/33] update pre-session notes routing --- ALUM/ALUM.xcodeproj/project.pbxproj | 16 ++ .../CustomNavBarContainerView.swift | 8 +- .../CustomNavBarPreferenceKeys.swift | 7 +- .../CustomNavBar/CustomNavLink.swift | 9 +- ALUM/ALUM/Constants/DevelopmentModels.swift | 27 +++ ALUM/ALUM/Models/CurrentUserModel.swift | 6 +- ALUM/ALUM/Services/UserService.swift | 2 +- ALUM/ALUM/ViewModels/QuestionViewModel.swift | 69 +----- .../CalendlyBooking/CalendlyBooking.swift | 5 +- ALUM/ALUM/Views/LoadingScreen.swift | 2 + ALUM/ALUM/Views/MenteeProfileScreen.swift | 6 - ALUM/ALUM/Views/MentorProfileScreen.swift | 22 +- .../PostSessionQuestionScreen.swift | 1 - .../PreSessionConfirmationScreen.swift | 17 +- .../PreSessionForm/PreSessionFormRouter.swift | 68 ++++++ .../PreSessionQuestionScreen.swift | 216 ++++++++---------- .../Views/PreSessionForm/PreSessionView.swift | 37 +-- .../SessionConfirmationScreen.swift | 4 +- ALUM/ALUM/Views/Routers/HomeTabRouter.swift | 30 +++ ALUM/ALUM/Views/Routers/LoggedInRouter.swift | 125 +++------- .../ALUM/Views/Routers/ProfileTabRouter.swift | 32 +++ ALUM/ALUM/Views/Routers/RootRouter.swift | 13 +- .../SessionDetailsScreen.swift | 101 ++++---- .../ViewPreSessionNotesPage.swift | 96 ++++---- ALUM/ALUM/Views/UnpairedScreen.swift | 44 ++++ backend/src/errors/internal.ts | 4 + backend/src/routes/user.ts | 13 +- backend/src/services/session.ts | 25 ++ 28 files changed, 545 insertions(+), 460 deletions(-) create mode 100644 ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift create mode 100644 ALUM/ALUM/Views/Routers/HomeTabRouter.swift create mode 100644 ALUM/ALUM/Views/Routers/ProfileTabRouter.swift create mode 100644 ALUM/ALUM/Views/UnpairedScreen.swift diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 41679e53..1c8d2051 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -8,6 +8,10 @@ /* Begin PBXBuildFile section */ 0723BB862A1C995700911948 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB852A1C995700911948 /* HomeScreen.swift */; }; + 0723BB8F2A1CDF7500911948 /* UnpairedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB8E2A1CDF7500911948 /* UnpairedScreen.swift */; }; + 0723BB912A1D4ADE00911948 /* PreSessionFormRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */; }; + 0723BB932A1D576800911948 /* ProfileTabRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB922A1D576800911948 /* ProfileTabRouter.swift */; }; + 0723BB952A1D578400911948 /* HomeTabRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0723BB942A1D578400911948 /* HomeTabRouter.swift */; }; 0726C9EF29DC4B120042A486 /* CustomErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0726C9EE29DC4B120042A486 /* CustomErrors.swift */; }; 0748208F29712921004AF547 /* ALUMApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748208E29712921004AF547 /* ALUMApp.swift */; }; 0748209129712921004AF547 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748209029712921004AF547 /* ContentView.swift */; }; @@ -128,6 +132,10 @@ /* Begin PBXFileReference section */ 0723BB852A1C995700911948 /* HomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 0723BB8E2A1CDF7500911948 /* UnpairedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpairedScreen.swift; sourceTree = ""; }; + 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreSessionFormRouter.swift; sourceTree = ""; }; + 0723BB922A1D576800911948 /* ProfileTabRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTabRouter.swift; sourceTree = ""; }; + 0723BB942A1D578400911948 /* HomeTabRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTabRouter.swift; sourceTree = ""; }; 0726C9EE29DC4B120042A486 /* CustomErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomErrors.swift; sourceTree = ""; }; 0748208B29712921004AF547 /* ALUM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ALUM.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0748208E29712921004AF547 /* ALUMApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALUMApp.swift; sourceTree = ""; }; @@ -342,6 +350,8 @@ children = ( 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */, 07E885352A19F0D300B7AD27 /* RootRouter.swift */, + 0723BB922A1D576800911948 /* ProfileTabRouter.swift */, + 0723BB942A1D578400911948 /* HomeTabRouter.swift */, ); path = Routers; sourceTree = ""; @@ -413,6 +423,7 @@ 0748209029712921004AF547 /* ContentView.swift */, 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */, 0723BB852A1C995700911948 /* HomeScreen.swift */, + 0723BB8E2A1CDF7500911948 /* UnpairedScreen.swift */, ); path = Views; sourceTree = ""; @@ -478,6 +489,7 @@ 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */, 0757552729FAA2D5008E73FB /* PreSessionView.swift */, 0757552C29FAA2D6008E73FB /* SessionConfirmationScreen.swift */, + 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */, ); path = PreSessionForm; sourceTree = ""; @@ -658,6 +670,7 @@ buildActionMask = 2147483647; files = ( 0757553C29FAA387008E73FB /* NotesService.swift in Sources */, + 0723BB912A1D4ADE00911948 /* PreSessionFormRouter.swift in Sources */, 0794653429DC536200B68EFD /* InputValidationComponent.swift in Sources */, 8095FE7229E41214006AA63C /* NavigationComponent.swift in Sources */, 07CA49932A1C50CC00A81153 /* DevelopmentModels.swift in Sources */, @@ -723,11 +736,13 @@ 9752A4EA2978B90F001E0AAB /* TextInputFieldComponent.swift in Sources */, 979303D829E87E0E0053C30E /* SessionModel.swift in Sources */, 0799ACE32A007E8A00EEAFA2 /* LoadingScreen.swift in Sources */, + 0723BB952A1D578400911948 /* HomeTabRouter.swift in Sources */, 80F5388A29AF6E8E00FB5E66 /* SignUpConfirmationMentorScreen.swift in Sources */, 979F812429B85DF900D6E964 /* SelectUniversityComponent.swift in Sources */, 0757553229FAA2D6008E73FB /* PreSessionQuestionScreen.swift in Sources */, 97F6CDF429BD317200DFBB99 /* TopicsOfInterest.swift in Sources */, 979F812029B8181400D6E964 /* SelectYearComponent.swift in Sources */, + 0723BB8F2A1CDF7500911948 /* UnpairedScreen.swift in Sources */, 075379F729B6A9F100A0DD5E /* TagEditor.swift in Sources */, 97E439F429AD97FA00F0B7C1 /* SignUpMenteeInfoScreen.swift in Sources */, 0748209129712921004AF547 /* ContentView.swift in Sources */, @@ -735,6 +750,7 @@ 974D62CF29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift in Sources */, 979303CB29E7E9460053C30E /* SessionDetailViewModel.swift in Sources */, 0757553A29FAA380008E73FB /* QuestionViewModel.swift in Sources */, + 0723BB932A1D576800911948 /* ProfileTabRouter.swift in Sources */, 80210EC129B95F24008B912A /* MenteeCard.swift in Sources */, 80210ECC29BA9EBC008B912A /* MentorCard.swift in Sources */, 0793B4222A19FA7200AF78C8 /* CustomNavBarPreferenceKeys.swift in Sources */, diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index 2ff939f7..2136cf24 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -9,14 +9,14 @@ import SwiftUI struct CustomNavBarContainerView: View { let content: Content - @State private var showBackButton: Bool = true - @State private var title: String = "Title" - @State private var isPurple: Bool = true + @State private var showBackButton: Bool = false + @State private var title: String = "" + @State private var isPurple: Bool = false init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { - VStack(spacing: 0) { + return VStack(spacing: 0) { CustomNavBarView(showBackButton: showBackButton, title: title, isPurple: isPurple) content .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift index b5c85c64..53cf4546 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -7,9 +7,6 @@ import Foundation import SwiftUI -//@State private var showBackButton: Bool = true -//@State private var title: String = "Title" -//@State private var isPurple: Bool = true struct CustomNavBarTitlePreferenceKey: PreferenceKey { static var defaultValue: String = "" @@ -20,7 +17,7 @@ struct CustomNavBarTitlePreferenceKey: PreferenceKey { } struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { - static var defaultValue: Bool = true + static var defaultValue: Bool = false static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() @@ -28,7 +25,7 @@ struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { } struct CustomNavBarBackButtonHiddenPreferenceKey: PreferenceKey { - static var defaultValue: Bool = false + static var defaultValue: Bool = true static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift index 8a18740b..3fb4d961 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -16,10 +16,13 @@ struct CustomNavLink: View { } var body: some View { NavigationLink( - destination: CustomNavBarContainerView(content: { - destination + destination: + CustomNavBarContainerView(content: { + destination }) - .navigationBarHidden(true) + .navigationBarHidden(true) + .customNavigationBarBackButtonHidden(false) + , label: { label }) diff --git a/ALUM/ALUM/Constants/DevelopmentModels.swift b/ALUM/ALUM/Constants/DevelopmentModels.swift index 32da3810..497313f6 100644 --- a/ALUM/ALUM/Constants/DevelopmentModels.swift +++ b/ALUM/ALUM/Constants/DevelopmentModels.swift @@ -40,4 +40,31 @@ class DevelopmentModels { status: nil, whyPaired: Optional("Modified Why Paired") ) + + static var preSessionFormModel: [Question] = [ + Question( + question: "What topic(s) would you like to discuss?", + type: "bullet", + id: "3486cca0ff5e75620cb5cded01041c45751d0ac93a068de3f4cd925b87cdff5f", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ), + Question( + question: "Do you have any specifc question(s)?", + type: "bullet", + id: "f929836eee49ca458ae32ad89164bdb31e5749a5606c15b147a61d69c7cac8fd", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ), + Question( + question: "Anything else that you want your mentor to know?", + type: "text", + id: "9bb08261232461b5dfbdea48578c6054adf7fd8639d815b4143080d0c16ec590", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ) + ] } diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index a5a269f5..59e77bd3 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -22,8 +22,8 @@ class CurrentUserModel: ObservableObject { @Published var isLoggedIn: Bool @Published var status: String? @Published var showTabBar: Bool - - @Published var upcomingSessionId: String? + + @Published var sessionId: String? @Published var pairedMentorId: String? @Published var pairedMenteeId: String? @@ -122,7 +122,7 @@ class CurrentUserModel: ObservableObject { } DispatchQueue.main.async { - self.upcomingSessionId = userData.upcomingSessionId + self.sessionId = userData.sessionId } } diff --git a/ALUM/ALUM/Services/UserService.swift b/ALUM/ALUM/Services/UserService.swift index a0ee2bb0..bb02fd0b 100644 --- a/ALUM/ALUM/Services/UserService.swift +++ b/ALUM/ALUM/Services/UserService.swift @@ -35,7 +35,7 @@ struct MentorPostData: Codable { struct SelfGetData: Decodable { var status: String - var upcomingSessionId: String? + var sessionId: String? var pairedMentorId: String? var pairedMenteeId: String? } diff --git a/ALUM/ALUM/ViewModels/QuestionViewModel.swift b/ALUM/ALUM/ViewModels/QuestionViewModel.swift index 0a0dcec7..227f6802 100644 --- a/ALUM/ALUM/ViewModels/QuestionViewModel.swift +++ b/ALUM/ALUM/ViewModels/QuestionViewModel.swift @@ -19,59 +19,6 @@ final class QuestionViewModel: ObservableObject { @Published var submitSuccess: Bool = false @Published var missedOption: String = "" - func loadTestData() { - print("load test data") - var question1 = Question(question: "Testing Question 1", - type: "text", - id: "1", - answerBullet: [], - answerCheckboxBullet: [], - answerParagraph: "Testing Answer 1") - var question2 = Question(question: "Testing Question 2", - type: "text", - id: "2", - answerBullet: [], - answerCheckboxBullet: [], - answerParagraph: "Testing Answer 2") - var question3 = Question(question: "Testing Question 3", - type: "bullet", - id: "3", - answerBullet: - ["Testing a really long line so I can make sure it wraps around as it should", - "Answer", "3"], - answerCheckboxBullet: [], - answerParagraph: "") - var question4 = Question(question: "Testing Question 4", - type: "bullet", - id: "4", - answerBullet: - ["Some other possible answers", - "Blah Blah Blah", - "Longer answer to make this look long Longer answer to make this look long"], - answerCheckboxBullet: [], - answerParagraph: "") - var question5 = Question(question: "Testing Question 5", - type: "checkbox-bullet", - id: "5", - answerBullet: [], - answerCheckboxBullet: - [CheckboxBullet(content: "some content", status: "unchecked"), - CheckboxBullet(content: "more content", status: "checked"), - CheckboxBullet(content: "a bullet here", status: "bullet")], - answerParagraph: "") - var question6 = Question(question: "Testing Question 6", - type: "bullet", - id: "5", - answerBullet: ["bullet 1", "bullet 2"], - answerCheckboxBullet: [], - answerParagraph: "") - - self.questionList.append(question1); self.questionList.append(question2) - self.questionList.append(question3); self.questionList.append(question4) - self.questionList.append(question6) - self.isLoading = false - } - func submitNotesPatch(noteID: String) async throws { var notesData: [QuestionPatchData] = [] @@ -87,14 +34,21 @@ final class QuestionViewModel: ObservableObject { try await NotesService.shared.patchNotes(noteId: noteID, data: notesData) } - func loadNotes(notesID: String) async throws { - var notesData: [QuestionGetData] = try await NotesService.shared.getNotes(noteId: notesID) + func fetchNotes(noteId: String) async throws { + DispatchQueue.main.async { + self.isLoading = true + } + let notesData: [QuestionGetData] = try await NotesService.shared.getNotes(noteId: noteId) + var newQuestions: [Question] = [] for question in notesData { var questionToAdd: Question = Question(question: question.question, type: question.type, id: question.id) question.answer.toRaw(question: &questionToAdd) - self.questionList.append(questionToAdd) + newQuestions.append(questionToAdd) + } + DispatchQueue.main.async { + self.isLoading = false + self.questionList = newQuestions } - self.isLoading = false } func loadPostNotes(notesID: String, otherNotesID: String) async throws { @@ -113,6 +67,7 @@ final class QuestionViewModel: ObservableObject { self.questionListOther.append(questionToAdd) } self.isLoading = false + // TODO set currentIndex to 0 } func nextQuestion() { diff --git a/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift b/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift index 88617c67..520a5057 100644 --- a/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift +++ b/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift @@ -53,7 +53,10 @@ struct CalendlyView: UIViewRepresentable { let messageBody = "\(message.body)" // Method should be called with proper mentor, mentee ids let result = try await - SessionService().postSessionWithId(calendlyURI: messageBody) + SessionService.shared.postSessionWithId(calendlyURI: messageBody) + DispatchQueue.main.async { + CurrentUserModel.shared.isLoading = true + } } catch { throw AppError.internalError(.unknownError, message: "Error posting a new session") } diff --git a/ALUM/ALUM/Views/LoadingScreen.swift b/ALUM/ALUM/Views/LoadingScreen.swift index 3a4cb19b..e2cd4ebd 100644 --- a/ALUM/ALUM/Views/LoadingScreen.swift +++ b/ALUM/ALUM/Views/LoadingScreen.swift @@ -13,8 +13,10 @@ struct LoadingView: View { var body: some View { VStack { + Spacer() Text(text) ProgressView() + Spacer() } } } diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index dfd37220..89b79811 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -148,12 +148,6 @@ extension MenteeProfileScreen { } } } -//struct MenteeProfileView_Previews: PreviewProvider { -// static var previews: some View { -// MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") -// } -//} -// struct MenteeProfileView_Previews: PreviewProvider { static var previews: some View { diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 86868150..6e8e0a98 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -23,7 +23,6 @@ struct MentorProfileScreen: View { } else { content .customNavigationIsPurple(scrollAtTop) - .navigationBarHidden(true) .padding(.top, 0) } } @@ -79,26 +78,7 @@ struct MentorProfileScreen: View { .padding(.bottom, 8) .edgesIgnoringSafeArea(.bottom) } - ZStack { - if viewModel.selfView! { - // params currently placeholders for later navigation - if scrollAtTop { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) - .background(Color("ALUM Primary Purple")) - } else { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) - .background(.white) - } - } - else { - if scrollAtTop { - Rectangle() - .frame(height: 10) - .foregroundColor(Color("ALUM Primary Purple")) - .frame(maxHeight: .infinity, alignment: .top) - } - } - } + } } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift index 76c6339e..b5e7e41e 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift @@ -35,7 +35,6 @@ struct PostSessionQuestionScreen: View { } .edgesIgnoringSafeArea(.bottom) - .applyPostSessionScreenHeaderModifier() } @ViewBuilder diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift index 91943915..352c4392 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift @@ -11,15 +11,10 @@ import SwiftUI struct PreSessionConfirmationScreen: View { @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var notesID: String = "" + var notesID: String var body: some View { VStack { - StaticProgressBarComponent(nodes: viewModel.questionList.count, - filledNodes: viewModel.questionList.count, activeNode: 0) - .background(Color.white) - ScrollView { content } @@ -30,17 +25,16 @@ struct PreSessionConfirmationScreen: View { .background(Rectangle().fill(Color.white).shadow(radius: 8)) } .edgesIgnoringSafeArea(.bottom) - .applyPreSessionScreenHeaderModifier() } var footer: some View { HStack { Button { - dismiss() + viewModel.prevQuestion() } label: { HStack { Image(systemName: "arrow.left") - Text("Back") + ALUMText(text: "Back") } } .buttonStyle(OutlinedButtonStyle()) @@ -57,9 +51,10 @@ struct PreSessionConfirmationScreen: View { } } } label: { - Text("Save") + ALUMText(text: "Save", textColor: ALUMColor.white) } .buttonStyle(FilledInButtonStyle()) + // TODO change this to custom nav link NavigationLink(destination: SessionConfirmationScreen( text: ["Pre-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), @@ -135,6 +130,6 @@ struct PreSessionConfirmationScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - PreSessionConfirmationScreen(viewModel: viewModel) + PreSessionConfirmationScreen(viewModel: viewModel, notesID: "6464276b6f05d9703f069761") } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift new file mode 100644 index 00000000..af9a983a --- /dev/null +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift @@ -0,0 +1,68 @@ +// +// PreSessionFormRouter.swift +// ALUM +// +// Created by Aman Aggarwal on 5/23/23. +// + +import SwiftUI + +struct PreSessionFormRouter: View { + @StateObject private var viewModel = QuestionViewModel() + + var notesID: String + var otherName: String + var date: String + var time: String + + var body: some View { + loadingAbstraction + .customNavBarItems( + title: "\(date) Pre-session Notes", + isPurple: false, + backButtonHidden: false + ) + } + + var loadingAbstraction: some View { + print("isLoading \(viewModel.isLoading)") + return Group { + if viewModel.isLoading { + LoadingView(text: "PreSessionFormRouter \(notesID)") + .onAppear { + Task { + do { + try await viewModel.fetchNotes(noteId: notesID) + } catch { + print("ERROR PreSessionFormRouter \(error)") + } + } + } + } else { + loadedView + } + } + } + + var loadedView: some View { + return VStack { + DynamicProgressBarComponent(nodes: $viewModel.questionList.count + 1, + filledNodes: $viewModel.currentIndex, activeNode: $viewModel.currentIndex) + .padding() + .background(Color.white) + if viewModel.currentIndex < viewModel.questionList.count { + PreSessionQuestionScreen(viewModel: viewModel, otherUser: otherName, date: date, time: time) + } else { + PreSessionConfirmationScreen(viewModel: viewModel, notesID: notesID) + } + } + } +} + +struct PreSessionFormRouter_Previews: PreviewProvider { + static var previews: some View { + CustomNavView { + PreSessionFormRouter(notesID: "6464276b6f05d9703f069761", otherName: "Mentor", date: "5/23", time: "9pm") + } + } +} diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift index 46971cee..17f04633 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift @@ -10,152 +10,136 @@ import SwiftUI struct PreSessionQuestionScreen: View { @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var notesID: String = "" - @State var otherUser: String = "Mentor" - @State var date: String = "date" - @State var time: String = "time" + + var otherUser: String + var date: String + var time: String var body: some View { VStack { - DynamicProgressBarComponent(nodes: $viewModel.questionList.count, - filledNodes: $viewModel.currentIndex, activeNode: $viewModel.currentIndex) - .padding() - .background(Color.white) - ScrollView { content } - footer - .padding(.horizontal, 16) - .padding(.top, 32) - .padding(.bottom, 40) - .background(Rectangle().fill(Color.white).shadow(radius: 8)) - } .edgesIgnoringSafeArea(.bottom) - .applyPreSessionScreenHeaderModifier() } - @ViewBuilder - var footer: some View { - if !viewModel.lastQuestion { - if viewModel.currentIndex == 0 { - Button { - viewModel.nextQuestion() - } label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } - .buttonStyle(FilledInButtonStyle()) - } else { - HStack { - Button { - viewModel.prevQuestion() - } label: { - HStack { - Image(systemName: "arrow.left") - Text("Back") - } - } - .buttonStyle(OutlinedButtonStyle()) - - Spacer() + - Button { - viewModel.nextQuestion() - } label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } - .buttonStyle(FilledInButtonStyle()) - } + var content: some View { + var currentIndex = viewModel.currentIndex + if viewModel.currentIndex >= viewModel.questionList.count { + currentIndex = viewModel.questionList.count - 1 + } + let currentQuestion = viewModel.questionList[currentIndex] + + print("currentQuestion", currentQuestion) + return VStack { + if viewModel.currentIndex == 0 { + firstQuestionBanner } - } else { - HStack { - Button { - viewModel.prevQuestion() - } label: { - HStack { - Image(systemName: "arrow.left") - Text("Back") - } - } - .buttonStyle(OutlinedButtonStyle()) - - Spacer() - NavigationLink( - destination: PreSessionConfirmationScreen(viewModel: viewModel, notesID: notesID), - label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } + if currentQuestion.type == "text" { + ParagraphInput( + question: currentQuestion.question, + text: $viewModel.questionList[currentIndex].answerParagraph ) - .buttonStyle(FilledInButtonStyle()) + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.top, 8) + } else if currentQuestion.type == "bullet" { + ALUMText(text: currentQuestion.question, textColor: ALUMColor.primaryBlue) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.bottom, 16) + .padding(.top, 8) + + BulletsView(bullets: $viewModel.questionList[currentIndex].answerBullet, + question: currentQuestion.question) } } } + + var firstQuestionBanner: some View { + ZStack { + RoundedRectangle(cornerRadius: 12) + .fill(Color("ALUM Light Blue")) + Text("You have successfully booked a session with \(otherUser) on \(date) at \(time).") + .font(.custom("Metropolis-Regular", size: 17)) + .lineSpacing(10) + .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 32) + .padding(.top, 8) + } +} - // viewModel.questionList[index].answerBullet - var content: some View { - VStack { +// Footer Code goes here +extension PreSessionQuestionScreen { + @ViewBuilder + var footer: some View { + Group { if viewModel.currentIndex == 0 { - ZStack { - RoundedRectangle(cornerRadius: 12) - .fill(Color("ALUM Light Blue")) - Text("You have successfully booked a session with \(otherUser) on \(date) at \(time).") - .font(.custom("Metropolis-Regular", size: 17)) - .lineSpacing(10) - .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) + footerForFirstQuestion + } else { + footerWithBackAndContinue + } + } + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 40) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) + } + + var footerForFirstQuestion: some View { + Button { + viewModel.nextQuestion() + } label: { + HStack { + ALUMText(text: "Continue", textColor: ALUMColor.white) + Image(systemName: "arrow.right") + .font(.system(size: 17)) + .foregroundColor(Color.white) + } + } + .buttonStyle(FilledInButtonStyle()) + } + + var footerWithBackAndContinue: some View { + HStack { + Button { + viewModel.prevQuestion() + } label: { + HStack { + Image(systemName: "arrow.left") + Text("Back") } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 32) - .padding(.top, 8) } + .buttonStyle(OutlinedButtonStyle()) + + Spacer() - if viewModel.questionList[viewModel.currentIndex].type == "text" { - ParagraphInput(question: viewModel.questionList[viewModel.currentIndex].question, - text: $viewModel.questionList[viewModel.currentIndex].answerParagraph) - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.top, 8) - } else if viewModel.questionList[viewModel.currentIndex].type == "bullet" { - Text(viewModel.questionList[viewModel.currentIndex].question) - .foregroundColor(Color("ALUM Dark Blue")) - .font(Font.custom("Metropolis-Regular", size: 17)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.bottom, 16) - .padding(.top, 8) - BulletsView(bullets: $viewModel.questionList[viewModel.currentIndex].answerBullet, - question: viewModel.questionList[viewModel.currentIndex].question) + Button { + viewModel.nextQuestion() + } label: { + HStack { + ALUMText(text: "Continue", textColor: ALUMColor.white) + Image(systemName: "arrow.right") + .font(.system(size: 17)) + .foregroundColor(Color.white) + } } + .buttonStyle(FilledInButtonStyle()) } } } - struct PreSessionQuestionScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - PreSessionQuestionScreen(viewModel: viewModel) + PreSessionQuestionScreen(viewModel: viewModel, otherUser: "Mentor", date: "5/23", time: "9pm") } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift index 644d36b9..a5978565 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift @@ -7,29 +7,6 @@ import SwiftUI -struct PreSessionScreenHeaderModifier: ViewModifier { - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "XXX", - backDestination: LoginScreen(), - title: "Pre-session Notes", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyPreSessionScreenHeaderModifier() -> some View { - self.modifier(PreSessionScreenHeaderModifier()) - } -} - struct PreSessionView: View { @StateObject private var viewModel = QuestionViewModel() @@ -41,19 +18,23 @@ struct PreSessionView: View { @State var time: String = "" var body: some View { - print(viewModel.isLoading) + content + .customNavBarItems( + title: "\(date) Pre-session Notes", + isPurple: false, + backButtonHidden: false + ) + } + var content: some View { return Group { if !viewModel.isLoading { PreSessionQuestionScreen( viewModel: viewModel, - notesID: notesID, otherUser: otherName, date: date, time: time ) - .navigationBarTitle("", displayMode: .inline) - .navigationBarHidden(true) } else { Text("Loading...") .navigationBarTitle("") @@ -61,7 +42,7 @@ struct PreSessionView: View { .onAppear { Task { do { - try await viewModel.loadNotes(notesID: notesID) + try await viewModel.fetchNotes(noteId: notesID) } catch { print("Error") } diff --git a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift index 1f3751e6..e2562807 100644 --- a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift @@ -39,8 +39,8 @@ struct SessionConfirmationScreen: View { .padding(16) Spacer() - NavigationLink( - destination: Text("TODO Blank").navigationBarHidden(true), + CustomNavLink( + destination: LoggedInRouter(defaultSelection: 0), label: { HStack { Text(text[2]) diff --git a/ALUM/ALUM/Views/Routers/HomeTabRouter.swift b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift new file mode 100644 index 00000000..d27bb192 --- /dev/null +++ b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift @@ -0,0 +1,30 @@ +// +// HomeTabRouter.swift +// ALUM +// +// Created by Aman Aggarwal on 5/23/23. +// + +import SwiftUI + +/// For MVP, our home tab is either a session details page or just a screen with pairing info and button to book a session +struct HomeTabRouter: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + + var body: some View { + Group { + if currentUser.sessionId == nil { + HomeScreen() + } else { + SessionDetailsScreen(sessionId: currentUser.sessionId!) + .customNavigationBarBackButtonHidden(true) + } + } + } +} + +struct HomeTabRouter_Previews: PreviewProvider { + static var previews: some View { + HomeTabRouter() + } +} diff --git a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift index b09b180f..5a8864cf 100644 --- a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -7,58 +7,15 @@ import SwiftUI -struct ProfileTabRouter: View { - @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - - var body: some View { - NavigationView { - Group { - switch self.currentUser.role { - case .some(UserRole.mentor): - MentorProfileScreen(uID: self.currentUser.uid!) - case .some(UserRole.mentee): - MenteeProfileScreen(uID: self.currentUser.uid!) - case .none: - Text("Internal Error: User Role is nil") - } - } - } - } -} - -struct HomeTabRouter: View { - @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - - var body: some View { - CustomNavView { - if currentUser.upcomingSessionId == nil { - HomeScreen() - } else { - SessionDetailsScreen(sessionId: currentUser.upcomingSessionId!) - } - } - } -} - -struct PlaceHolderHomeScreen: View { - var body: some View { - NavigationView { - VStack { - Text("PlaceHolderHomeScreen") - Button("Sign Out", action: { - FirebaseAuthenticationService.shared.logout() - }) - } - } - } -} - +// Contains the custom tab view struct LoggedInRouter: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared - // (todo) Needs to be customized to match our design - @State private var selection = 0 - init() { + + @State private var selection: Int + + init(defaultSelection: Int = 0) { UITabBar.appearance().backgroundColor = UIColor(Color.white) // custom color. + selection = defaultSelection } let tabItems = [ @@ -66,54 +23,42 @@ struct LoggedInRouter: View { TabBarItem(iconName: "GrayCircle", title: "Profile") ] + + + // once user is approved and paired var body: some View { - Group { - if currentUser.status == "under review" { - if currentUser.role == .mentee { - LoginReviewPage(text: - ["Application is under review", - "It usually takes 3-5 days to process your application as a mentee."]) - } else if currentUser.role == .mentor { - LoginReviewPage(text: - ["Application is under review", - "It usually takes 3-5 days to process your application as a mentor."]) - } - } else if currentUser.status == "approved" { - if currentUser.role == .mentee { - LoginReviewPage(text: - ["Matching you with a mentor", - "We are looking for a perfect mentor for you. Please allow us some time!"]) - } else if currentUser.role == .mentor { - LoginReviewPage(text: - ["Matching you with a mentee", - "We are looking for a perfect mentee for you. Please allow us some time!"]) + return ZStack(alignment: .bottom) { + VStack { + Spacer() + if currentUser.showTabBar { + tabsDisplay } - } else { - pairedUserView } + content + .padding(.bottom, 50) } } - - // once user is approved and paired - var pairedUserView: some View { + + var content: some View { VStack(spacing: 0) { - switch selection { - case 0: - HomeTabRouter() - .onAppear(perform: { - currentUser.showTabBar = true - }) - case 1: - ProfileTabRouter() - .onAppear(perform: { - currentUser.showTabBar = true - }) - default: - Text("Error") - } - if currentUser.showTabBar { - tabsDisplay + + Group { + switch selection { + case 0: + HomeTabRouter() + case 1: + ProfileTabRouter() + default: + Text("Error") + } + } + .onAppear(perform: { + currentUser.showTabBar = true + }) + .onDisappear(perform: { + currentUser.showTabBar = false + }) } } diff --git a/ALUM/ALUM/Views/Routers/ProfileTabRouter.swift b/ALUM/ALUM/Views/Routers/ProfileTabRouter.swift new file mode 100644 index 00000000..81748960 --- /dev/null +++ b/ALUM/ALUM/Views/Routers/ProfileTabRouter.swift @@ -0,0 +1,32 @@ +// +// ProfileTabRouter.swift +// ALUM +// +// Created by Aman Aggarwal on 5/23/23. +// + +import SwiftUI + +struct ProfileTabRouter: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + + var body: some View { + Group { + switch self.currentUser.role { + case .some(UserRole.mentor): + MentorProfileScreen(uID: self.currentUser.uid!) + case .some(UserRole.mentee): + MenteeProfileScreen(uID: self.currentUser.uid!) + case .none: + Text("Internal Error: User Role is nil") + } + } + .customNavigationTitle("My Profile") + } +} + +struct ProfileTabRouter_Previews: PreviewProvider { + static var previews: some View { + ProfileTabRouter() + } +} diff --git a/ALUM/ALUM/Views/Routers/RootRouter.swift b/ALUM/ALUM/Views/Routers/RootRouter.swift index 85dd109d..c9fa3a60 100644 --- a/ALUM/ALUM/Views/Routers/RootRouter.swift +++ b/ALUM/ALUM/Views/Routers/RootRouter.swift @@ -22,14 +22,23 @@ struct RootRouter: View { NavigationView { LoginScreen() } + } else if self.currentUser.status == "paired" { + CustomNavView { + LoggedInRouter(defaultSelection: 0) + } } else { - LoggedInRouter() + UnpairedScreen() } } } struct RootView_Previews: PreviewProvider { static var previews: some View { - RootRouter() + CurrentUserModel.shared.setCurrentUser(isLoading: true, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor) + return RootRouter().onAppear(perform: { + Task { + try await CurrentUserModel.shared.fetchUserInfoFromServer(userId: "6431b9a2bcf4420fe9825fe5", role: .mentor) + } + }) } } diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift index 08b36bc3..fbfbca17 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift @@ -20,6 +20,7 @@ struct SessionDetailsScreen: View { var body: some View { return loadingAbstraction + .customNavigationIsPurple(false) } var loadingAbstraction: some View { @@ -58,11 +59,7 @@ struct SessionDetailsScreen: View { return ScrollView { screenContent } - .customNavBarItems( - title: "\(session.dateShortHandString) Session with \(otherName)", - isPurple: false, - backButtonHidden: false - ) + .customNavigationTitle("\(session.dateShortHandString) Session with \(otherName)") .background(ALUMColor.beige.color) } @@ -201,44 +198,47 @@ struct SessionDetailsScreen: View { } if !formIsComplete { - NavigationLink { - PostSessionView( + CustomNavLink( + destination: PostSessionView( notesID: editableNoteId, otherNotesID: otherNoteId, otherName: otherName, date: session.dateShortHandString, time: session.startTimeString - ) - } label: { - ALUMText(text: "Complete Post-Session Notes", textColor: ALUMColor.white) - } + ), + label: { + ALUMText(text: "Complete Post-Session Notes", textColor: ALUMColor.white) + } + ) .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 5) } else { - NavigationLink { - ViewPostSessionNotesPage( + CustomNavLink( + destination: ViewPostSessionNotesPage( notesID: editableNoteId, otherNotesID: otherNoteId, otherName: otherName, date: session.dateShortHandString, time: session.startTimeString - ) - } label: { - ALUMText(text: "View Post-Session Notes", textColor: ALUMColor.white) - } + ), + label: { + ALUMText(text: "View Post-Session Notes", textColor: ALUMColor.white) + } + ) .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 5) } - NavigationLink { - ViewPreSessionNotesPage( + CustomNavLink( + destination: ViewPreSessionNotesPage( allowEditing: false, notesID: session.preSession, otherName: otherName - ) - } label: { - ALUMText(text: "View Pre-Session Notes") - } + ), + label: { + ALUMText(text: "View Pre-Session Notes") + } + ) .buttonStyle(OutlinedButtonStyle()) .padding(.bottom, 5) } @@ -257,7 +257,7 @@ extension SessionDetailsScreen { } .padding(.bottom, 5) - NavigationLink(destination: MenteeProfileScreen(uID: session.menteeId)) { + CustomNavLink(destination: MenteeProfileScreen(uID: session.menteeId)) { HorizontalMenteeCard( menteeId: session.menteeId, isEmpty: true @@ -297,11 +297,14 @@ extension SessionDetailsScreen { } .padding(.bottom, 20) - NavigationLink { - ViewPreSessionNotesPage(allowEditing: false, notesID: session.preSession, otherName: session.menteeName) - } label: { - ALUMText(text: "View Pre-Session Form", textColor: ALUMColor.white) - } + CustomNavLink( + destination: ViewPreSessionNotesPage( + allowEditing: false, + notesID: session.preSession, + otherName: session.menteeName + ), label: { + ALUMText(text: "View Pre-Session Form", textColor: ALUMColor.white) + }) .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 5) } @@ -322,7 +325,7 @@ extension SessionDetailsScreen { } .padding(.bottom, 5) - NavigationLink(destination: MentorProfileScreen(uID: session.mentorId)) { + CustomNavLink(destination: MentorProfileScreen(uID: session.mentorId)) { MentorCard(isEmpty: true, uID: session.mentorId) .padding(.bottom, 28) } @@ -376,32 +379,34 @@ extension SessionDetailsScreen { } if !session.preSessionCompleted { - NavigationLink { - PreSessionView( - notesID: session.preSession, - otherName: session.mentorName, - date: session.dateShortHandString, - time: session.startTimeString - ) - } label: { - ALUMText(text: "Complete Pre-Session Notes", textColor: ALUMColor.white) - } + + CustomNavLink( + destination: + PreSessionFormRouter( + notesID: session.preSession, + otherName: session.mentorName, + date: session.dateShortHandString, + time: session.startTimeString + ), + label: { + ALUMText(text: "Complete Pre-Session Notes", textColor: ALUMColor.white) + } + ) .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 5) } else { - NavigationLink { - ViewPreSessionNotesPage( + CustomNavLink( + destination: ViewPreSessionNotesPage( allowEditing: true, notesID: session.preSession, otherName: session.mentorName, date: session.dateShortHandString, time: session.startTimeString - ) - } label: { - ALUMText(text: "View Pre-Session Notes", textColor: ALUMColor.white) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) + ), label: { + ALUMText(text: "View Pre-Session Notes", textColor: ALUMColor.white) + }) + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 5) } } } diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift index 67b2b2b3..dedd7cd1 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift @@ -7,29 +7,6 @@ import SwiftUI -struct ViewPreSessionNotesModifier: ViewModifier { - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Pre-session Notes", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyViewPreSessionNotesModifier() -> some View { - self.modifier(ViewPreSessionNotesModifier()) - } -} - struct ViewPreSessionNotesPage: View { var allowEditing: Bool var notesID: String @@ -40,54 +17,63 @@ struct ViewPreSessionNotesPage: View { @StateObject var viewModel = QuestionViewModel() var body: some View { + loadingAbstraction + .customNavBarItems(title: "\(date) Pre-session Notes", isPurple: false, backButtonHidden: false) + } + + var loadingAbstraction: some View { Group { if !viewModel.isLoading { - VStack { - ScrollView { - content - } - - if allowEditing { - footer - .padding(.horizontal, 16) - .padding(.top, 32) - .padding(.bottom, 40) - .background(Rectangle().fill(Color.white).shadow(radius: 8)) - } - } - .edgesIgnoringSafeArea(.bottom) - .applyViewPreSessionNotesModifier() + loadedView } else { - ProgressView() + LoadingView(text: "ViewPreSessionNotesPage") } } .onAppear { Task { do { - try await viewModel.loadNotes(notesID: notesID) + try await viewModel.fetchNotes(noteId: notesID) } catch { print("Error") } } } } - - var footer: some View { - NavigationLink { - PreSessionQuestionScreen( - viewModel: viewModel, - notesID: notesID, - otherUser: otherName, - date: date, - time: time - ) - } label: { - HStack { - Image(systemName: "pencil.line") - Text("Edit") + + var loadedView: some View { + VStack { + ScrollView { + content + } + + if allowEditing { + footer + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 40) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) } } - .buttonStyle(FilledInButtonStyle()) + .edgesIgnoringSafeArea(.bottom) + } + + var footer: some View { + // TODO change navigation to pre-session router + return CustomNavLink ( + destination: + PreSessionFormRouter( + notesID: notesID, + otherName: otherName, + date: date, + time: time + ), + label: { + HStack { + Image(systemName: "pencil.line") + Text("Edit") + } + }) + .buttonStyle(FilledInButtonStyle()) } var content: some View { diff --git a/ALUM/ALUM/Views/UnpairedScreen.swift b/ALUM/ALUM/Views/UnpairedScreen.swift new file mode 100644 index 00000000..dad3dc38 --- /dev/null +++ b/ALUM/ALUM/Views/UnpairedScreen.swift @@ -0,0 +1,44 @@ +// +// UnpairedScreen.swift +// ALUM +// +// Created by Aman Aggarwal on 5/23/23. +// + +import SwiftUI + +struct UnpairedScreen: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + + var body: some View { + Group { + if currentUser.status == "under review" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentee."]) + } else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Application is under review", + "It usually takes 3-5 days to process your application as a mentor."]) + } + } else if currentUser.status == "approved" { + if currentUser.role == .mentee { + LoginReviewPage(text: + ["Matching you with a mentor", + "We are looking for a perfect mentor for you. Please allow us some time!"]) + } else if currentUser.role == .mentor { + LoginReviewPage(text: + ["Matching you with a mentee", + "We are looking for a perfect mentee for you. Please allow us some time!"]) + } + } + } + } +} + +struct UnpairedScreen_Previews: PreviewProvider { + static var previews: some View { + UnpairedScreen() + } +} diff --git a/backend/src/errors/internal.ts b/backend/src/errors/internal.ts index 5c267ca1..aeea2d11 100644 --- a/backend/src/errors/internal.ts +++ b/backend/src/errors/internal.ts @@ -16,6 +16,7 @@ const ERROR_ROLES_NOT_MENTOR_MENTEE_NOT_IMPLEMENTED = "Any roles other than mentor/mentee has not been implemented."; const ERROR_FINDING_PAIR = "There was an error getting the mentee/mentor pairing"; const ERROR_FINDING_UPCOMING_SESSION = "Error occured while finding some upcoming session" +const ERROR_FINDING_PAST_SESSION = "Error occured while finding some past session" export class InternalError extends CustomError { static ERROR_GETTING_MENTEE = new InternalError(0, 500, ERROR_GETTING_MENTEE); @@ -43,4 +44,7 @@ export class InternalError extends CustomError { static ERROR_FINDING_PAIR = new InternalError(9, 500, ERROR_FINDING_PAIR); static ERROR_FINDING_UPCOMING_SESSION = new InternalError(10, 500, ERROR_FINDING_UPCOMING_SESSION); + + static ERROR_FINDING_PAST_SESSION = new InternalError(11, 500, ERROR_FINDING_PAST_SESSION); + } diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 6ca5cb02..5d430baf 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -20,7 +20,7 @@ import { ServiceError } from "../errors/service"; import { verifyAuthToken } from "../middleware/auth"; import { defaultImageID } from "../config"; import { CustomError } from "../errors"; -import { getUpcomingSession } from "../services/session"; +import { getUpcomingSession, getLastSession } from "../services/session"; const router = express.Router(); @@ -385,6 +385,7 @@ router.get( const role = req.body.role; const getUpcomingSessionPromise = getUpcomingSession(userId, role) + const getPastSessionPromise = getLastSession(userId, role) if (role == "mentee") { // GET mentee document const mentee = await Mentee.findById(userId); @@ -398,15 +399,15 @@ router.get( }) } const getPairedMentorIdPromise = getMentorId(mentee.pairingId) - const [sessionId, pairedMentorId] = await Promise.all([getUpcomingSessionPromise, getPairedMentorIdPromise]) + const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([getUpcomingSessionPromise, getPastSessionPromise, getPairedMentorIdPromise]) console.log({ status: mentee.status, - upcomingSessionId: sessionId, + sessionId: upcomingSessionId ?? pastSessionId, pairedMentorId }) res.status(200).send({ status: mentee.status, - upcomingSessionId: sessionId, + sessionId: upcomingSessionId ?? pastSessionId, pairedMentorId }) } else if (role == "mentor") { @@ -426,11 +427,11 @@ router.get( // For MVP, we assume there is only 1 mentee 1 mentor pairing const getMenteeIdsPromise = getMenteeIdsPromises[0] - const [sessionId, pairedMenteeId] = await Promise.all([getUpcomingSessionPromise, getMenteeIdsPromise]); + const [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([getUpcomingSessionPromise, getPastSessionPromise, getMenteeIdsPromise]) res.status(200).send({ status: mentor.status, - upcomingSessionId: sessionId, + sessionId: upcomingSessionId ?? pastSessionId, pairedMenteeId }) } diff --git a/backend/src/services/session.ts b/backend/src/services/session.ts index 29b18fd9..3721d2e3 100644 --- a/backend/src/services/session.ts +++ b/backend/src/services/session.ts @@ -27,6 +27,31 @@ export async function getUpcomingSession(userId: string, role: 'mentor' | 'mente } } +export async function getLastSession(userId: string, role: 'mentor' | 'mentee'): Promise { + const now = new Date(); + + let matchField: string; + + if (role === 'mentor') { + matchField = 'mentorId'; + } else if (role === 'mentee') { + matchField = 'menteeId'; + } else { + throw ServiceError.INVALID_ROLE_WAS_FOUND; + } + + try { + const lastSession = await Session.findOne({ + endTime: { $lt: now }, + [matchField]: new Types.ObjectId(userId), + }).sort({ endTime: -1 }); + + return lastSession?._id; + } catch (error) { + throw InternalError.ERROR_FINDING_PAST_SESSION; + } +} + /** * Function takes in startTime and endTime date objects and formats it to give: * [ From 72fcdc401044875f15fb77f4de1b0b19f51a60aa Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 16:25:53 -0700 Subject: [PATCH 13/33] move settin of title preference key for profile to sessions and update navlink --- ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift | 2 +- ALUM/ALUM/Views/MenteeProfileScreen.swift | 1 - ALUM/ALUM/Views/MentorProfileScreen.swift | 6 ------ .../Views/SessionDetailsView/SessionDetailsScreen.swift | 7 +++++-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift index 3fb4d961..43d3e761 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -19,9 +19,9 @@ struct CustomNavLink: View { destination: CustomNavBarContainerView(content: { destination + .customNavigationBarBackButtonHidden(false) }) .navigationBarHidden(true) - .customNavigationBarBackButtonHidden(false) , label: { label diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index 89b79811..e9015f53 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -21,7 +21,6 @@ struct MenteeProfileScreen: View { } else { content .customNavigationIsPurple(scrollAtTop) - .navigationBarBackButtonHidden(true) .padding(.top, 0) } }.onAppear(perform: { diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 6e8e0a98..2c45dda2 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -166,12 +166,6 @@ extension MentorProfileScreen { uID: viewModel.mentor!.menteeIds![index] ) .customNavigationTitle("Mentee Profile") - .onAppear(perform: { - currentUser.showTabBar = false - }) - .onDisappear(perform: { - currentUser.showTabBar = true - }) ) { MenteeCard(isEmpty: true, uID: viewModel.mentor!.menteeIds![index]) .padding(.bottom, 15) diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift index fbfbca17..27d6d33c 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift @@ -257,7 +257,7 @@ extension SessionDetailsScreen { } .padding(.bottom, 5) - CustomNavLink(destination: MenteeProfileScreen(uID: session.menteeId)) { + CustomNavLink(destination: MenteeProfileScreen(uID: session.menteeId).customNavigationTitle("Mentee Profile")) { HorizontalMenteeCard( menteeId: session.menteeId, isEmpty: true @@ -325,7 +325,10 @@ extension SessionDetailsScreen { } .padding(.bottom, 5) - CustomNavLink(destination: MentorProfileScreen(uID: session.mentorId)) { + CustomNavLink(destination: + MentorProfileScreen(uID: session.mentorId) + .customNavigationTitle("Mentor Profile") + ) { MentorCard(isEmpty: true, uID: session.mentorId) .padding(.bottom, 28) } From a07a49045d4c64fa2768906c4a24e67cd6b37d54 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 19:09:49 -0700 Subject: [PATCH 14/33] use custom header for mentor/mentee screens during self view --- .../CustomNavBarContainerView.swift | 10 +++++++- .../CustomNavBarPreferenceKeys.swift | 12 ++++++++++ ALUM/ALUM/Views/MenteeProfileScreen.swift | 23 +++++++++++++++++++ ALUM/ALUM/Views/MentorProfileScreen.swift | 23 ++++++++++++++++++- ALUM/ALUM/Views/Routers/LoggedInRouter.swift | 3 +-- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index 2136cf24..bcca8f33 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -12,12 +12,16 @@ struct CustomNavBarContainerView: View { @State private var showBackButton: Bool = false @State private var title: String = "" @State private var isPurple: Bool = false + @State private var isHidden: Bool = false + init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { return VStack(spacing: 0) { - CustomNavBarView(showBackButton: showBackButton, title: title, isPurple: isPurple) + if !isHidden { + CustomNavBarView(showBackButton: showBackButton, title: title, isPurple: isPurple) + } content .frame(maxWidth: .infinity, maxHeight: .infinity) } @@ -33,6 +37,10 @@ struct CustomNavBarContainerView: View { value in self.showBackButton = !value }) + .onPreferenceChange(CustomNavBarIsHiddenPreferenceKey.self, perform: { + value in + self.isHidden = value + }) } } diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift index 53cf4546..0f2ad1b3 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -16,6 +16,14 @@ struct CustomNavBarTitlePreferenceKey: PreferenceKey { } } +struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { + static var defaultValue: Bool = false + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { static var defaultValue: Bool = false @@ -33,6 +41,10 @@ struct CustomNavBarBackButtonHiddenPreferenceKey: PreferenceKey { } extension View { + func customNavigationIsHidden(_ isHidden: Bool) -> some View { + preference(key: CustomNavBarIsHiddenPreferenceKey.self, value: isHidden) + } + func customNavigationTitle(_ title: String) -> some View { preference(key: CustomNavBarTitlePreferenceKey.self, value: title) } diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index e9015f53..cb2ecff2 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -61,6 +61,29 @@ struct MenteeProfileScreen: View { .padding(.bottom, 8) .edgesIgnoringSafeArea(.bottom) } + ZStack { + // Removing this Z-stack causes a white rectangle to appear between the top of screen + // and start of this screen due to GeometryReader + + if viewModel.selfView! { + // params like settings and edit profile currently placeholders for later navigation + if scrollAtTop { + ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) + .background(Color("ALUM Primary Purple")) + } else { + ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) + .background(.white) + } + } + else { + if scrollAtTop { + Rectangle() + .frame(height: 10) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxHeight: .infinity, alignment: .top) + } + } + } } } } diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 2c45dda2..0408ac27 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -78,7 +78,28 @@ struct MentorProfileScreen: View { .padding(.bottom, 8) .edgesIgnoringSafeArea(.bottom) } - + ZStack { + // Removing this Z-stack causes a white rectangle to appear between the top of screen + // and start of this screen due to GeometryReader + if viewModel.selfView! { + // params like settings and edit profile currently placeholders for later navigation + if scrollAtTop { + ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) + .background(Color("ALUM Primary Purple")) + } else { + ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) + .background(.white) + } + } + else { + if scrollAtTop { + Rectangle() + .frame(height: 10) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxHeight: .infinity, alignment: .top) + } + } + } } } } diff --git a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift index 5a8864cf..736d8431 100644 --- a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -23,8 +23,6 @@ struct LoggedInRouter: View { TabBarItem(iconName: "GrayCircle", title: "Profile") ] - - // once user is approved and paired var body: some View { return ZStack(alignment: .bottom) { @@ -48,6 +46,7 @@ struct LoggedInRouter: View { HomeTabRouter() case 1: ProfileTabRouter() + .customNavigationIsHidden(true) // We shall use our own header component here so that we can easily add the edit buttons default: Text("Error") } From 5e67d3ee0cf6f55d5cccb9c13937414c77f5ab81 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Tue, 23 May 2023 20:15:28 -0700 Subject: [PATCH 15/33] add a comment --- ALUM/ALUM/Views/Routers/LoggedInRouter.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift index 736d8431..13afeb7c 100644 --- a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -26,6 +26,10 @@ struct LoggedInRouter: View { // once user is approved and paired var body: some View { return ZStack(alignment: .bottom) { + // Turns out for preference keys to work you need to run the mutate preference key function from either the view directly + // inside the navigation view OR from an inner view which is traversed last. + // Swift runs the preference key functions in a DFS manner. That's why we had to display the tab bar this way so that content is + // the last item traversed VStack { Spacer() if currentUser.showTabBar { From b333a105798d5e99c706d1ee4ba609f14f60123f Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 14:17:57 -0700 Subject: [PATCH 16/33] update navigation for post session flow and refractor + rename SessionConfirmationScreen to ConfirmationScreen so that can be used everywhere --- ALUM/ALUM.xcodeproj/project.pbxproj | 34 +-- ALUM/ALUM/Constants/DevelopmentModels.swift | 35 +++ ALUM/ALUM/ViewModels/QuestionViewModel.swift | 42 ++-- ...nScreen.swift => ConfirmationScreen.swift} | 4 +- .../PreSessionForm/MissedSessionScreen.swift | 198 ----------------- .../PostSessionConfirmationScreen.swift | 203 ------------------ .../PostSessionQuestionScreen.swift | 183 ---------------- .../PreSessionForm/PostSessionView.swift | 77 ------- .../PreSessionConfirmationScreen.swift | 4 +- .../PreSessionForm/PreSessionFormRouter.swift | 2 +- .../PreSessionQuestionScreen.swift | 2 +- .../Views/PreSessionForm/PreSessionView.swift | 63 ------ .../SessionDetailsScreen.swift | 11 +- .../ViewPostSessionNotesPage.swift | 107 ++++----- .../ViewPreSessionNotesPage.swift | 2 +- backend/src/routes/notes.ts | 1 + 16 files changed, 135 insertions(+), 833 deletions(-) rename ALUM/ALUM/Views/{PreSessionForm/SessionConfirmationScreen.swift => ConfirmationScreen.swift} (96%) delete mode 100644 ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift delete mode 100644 ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift delete mode 100644 ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift delete mode 100644 ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift delete mode 100644 ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 1c8d2051..dca7e350 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -26,12 +26,10 @@ 0757552429FAA2AB008E73FB /* MultipleChoiceList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757551E29FAA2AB008E73FB /* MultipleChoiceList.swift */; }; 0757552D29FAA2D6008E73FB /* PostSessionQuestionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */; }; 0757552E29FAA2D6008E73FB /* PostSessionConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */; }; - 0757552F29FAA2D6008E73FB /* PreSessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552729FAA2D5008E73FB /* PreSessionView.swift */; }; 0757553029FAA2D6008E73FB /* PreSessionConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552829FAA2D5008E73FB /* PreSessionConfirmationScreen.swift */; }; - 0757553129FAA2D6008E73FB /* PostSessionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552929FAA2D5008E73FB /* PostSessionView.swift */; }; 0757553229FAA2D6008E73FB /* PreSessionQuestionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */; }; 0757553329FAA2D6008E73FB /* MissedSessionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */; }; - 0757553429FAA2D6008E73FB /* SessionConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552C29FAA2D6008E73FB /* SessionConfirmationScreen.swift */; }; + 0757553429FAA2D6008E73FB /* ConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757552C29FAA2D6008E73FB /* ConfirmationScreen.swift */; }; 0757553829FAA350008E73FB /* QuestionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553729FAA350008E73FB /* QuestionModel.swift */; }; 0757553A29FAA380008E73FB /* QuestionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553929FAA380008E73FB /* QuestionViewModel.swift */; }; 0757553C29FAA387008E73FB /* NotesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0757553B29FAA387008E73FB /* NotesService.swift */; }; @@ -50,6 +48,7 @@ 07CA49962A1C53DA00A81153 /* ALUMColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49942A1C53DA00A81153 /* ALUMColor.swift */; }; 07CA49972A1C53DA00A81153 /* ALUMText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49952A1C53DA00A81153 /* ALUMText.swift */; }; 07E885362A19F0D300B7AD27 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E885352A19F0D300B7AD27 /* RootRouter.swift */; }; + 07ECF5F72A2279940093C37B /* PostSessionFormRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07ECF5F62A2279940093C37B /* PostSessionFormRouter.swift */; }; 2A638880299FF3BC00F9EA97 /* DrawerContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */; }; 2A8E07E2297B4FB0001AA153 /* OutlinedButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */; }; 2ADCE5BF299DBD570069802F /* ParagraphInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ADCE5BE299DBD570069802F /* ParagraphInput.swift */; }; @@ -151,12 +150,10 @@ 0757551E29FAA2AB008E73FB /* MultipleChoiceList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleChoiceList.swift; sourceTree = ""; }; 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostSessionQuestionScreen.swift; sourceTree = ""; }; 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostSessionConfirmationScreen.swift; sourceTree = ""; }; - 0757552729FAA2D5008E73FB /* PreSessionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreSessionView.swift; sourceTree = ""; }; 0757552829FAA2D5008E73FB /* PreSessionConfirmationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreSessionConfirmationScreen.swift; sourceTree = ""; }; - 0757552929FAA2D5008E73FB /* PostSessionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PostSessionView.swift; sourceTree = ""; }; 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreSessionQuestionScreen.swift; sourceTree = ""; }; 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MissedSessionScreen.swift; sourceTree = ""; }; - 0757552C29FAA2D6008E73FB /* SessionConfirmationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionConfirmationScreen.swift; sourceTree = ""; }; + 0757552C29FAA2D6008E73FB /* ConfirmationScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfirmationScreen.swift; sourceTree = ""; }; 0757553729FAA350008E73FB /* QuestionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuestionModel.swift; sourceTree = ""; }; 0757553929FAA380008E73FB /* QuestionViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuestionViewModel.swift; sourceTree = ""; }; 0757553B29FAA387008E73FB /* NotesService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesService.swift; sourceTree = ""; }; @@ -174,6 +171,7 @@ 07CA49942A1C53DA00A81153 /* ALUMColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALUMColor.swift; sourceTree = ""; }; 07CA49952A1C53DA00A81153 /* ALUMText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALUMText.swift; sourceTree = ""; }; 07E885352A19F0D300B7AD27 /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; + 07ECF5F62A2279940093C37B /* PostSessionFormRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSessionFormRouter.swift; sourceTree = ""; }; 2A63887F299FF3BC00F9EA97 /* DrawerContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerContainer.swift; sourceTree = ""; }; 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutlinedButtonStyle.swift; sourceTree = ""; }; 2ADCE5BE299DBD570069802F /* ParagraphInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParagraphInput.swift; sourceTree = ""; }; @@ -356,6 +354,17 @@ path = Routers; sourceTree = ""; }; + 07ECF5F82A22A94A0093C37B /* PostSessionForm */ = { + isa = PBXGroup; + children = ( + 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */, + 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */, + 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */, + 07ECF5F62A2279940093C37B /* PostSessionFormRouter.swift */, + ); + path = PostSessionForm; + sourceTree = ""; + }; 07F9A50C297180D700BC11A8 /* Components */ = { isa = PBXGroup; children = ( @@ -411,6 +420,7 @@ 07F9A50E297180F000BC11A8 /* Views */ = { isa = PBXGroup; children = ( + 07ECF5F82A22A94A0093C37B /* PostSessionForm */, 979303D029E7EA680053C30E /* SessionDetailsView */, 07E885342A19F0B800B7AD27 /* Routers */, 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */, @@ -424,6 +434,7 @@ 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */, 0723BB852A1C995700911948 /* HomeScreen.swift */, 0723BB8E2A1CDF7500911948 /* UnpairedScreen.swift */, + 0757552C29FAA2D6008E73FB /* ConfirmationScreen.swift */, ); path = Views; sourceTree = ""; @@ -481,14 +492,8 @@ 97A31AC729BAFA7600B79D30 /* PreSessionForm */ = { isa = PBXGroup; children = ( - 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */, - 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */, - 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */, - 0757552929FAA2D5008E73FB /* PostSessionView.swift */, 0757552829FAA2D5008E73FB /* PreSessionConfirmationScreen.swift */, 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */, - 0757552729FAA2D5008E73FB /* PreSessionView.swift */, - 0757552C29FAA2D6008E73FB /* SessionConfirmationScreen.swift */, 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */, ); path = PreSessionForm; @@ -692,7 +697,6 @@ 0793B41E2A19FA7200AF78C8 /* CustomNavBarContainerView.swift in Sources */, 97E439F229AD97B100F0B7C1 /* SignUpJoinAsScreen.swift in Sources */, 979303D629E872190053C30E /* FormIncompleteComponent.swift in Sources */, - 0757552F29FAA2D6008E73FB /* PreSessionView.swift in Sources */, 97E439F629AD983C00F0B7C1 /* SignUpMentorInfoScreen.swift in Sources */, 97EB2BB029B59D19001988D9 /* SignUpFlowErrorFunctions.swift in Sources */, 8095FE7529E4153C006AA63C /* ProfileHeaderComponent.swift in Sources */, @@ -702,7 +706,6 @@ 97E439F029AD95FA00F0B7C1 /* SignUpSetUpScreen.swift in Sources */, 804AE4AD297B1DA4000B08F2 /* FilledInButtonStyle.swift in Sources */, 0757552429FAA2AB008E73FB /* MultipleChoiceList.swift in Sources */, - 0757553129FAA2D6008E73FB /* PostSessionView.swift in Sources */, 07CA49972A1C53DA00A81153 /* ALUMText.swift in Sources */, 0757553029FAA2D6008E73FB /* PreSessionConfirmationScreen.swift in Sources */, 0726C9EF29DC4B120042A486 /* CustomErrors.swift in Sources */, @@ -722,12 +725,13 @@ 97E439FA29AD98BB00F0B7C1 /* SignUpGradeComponent.swift in Sources */, 8045EEB129F6844B00BD179C /* ServiceHelper.swift in Sources */, 97EB2BB229B5C63B001988D9 /* SignUpViewModel.swift in Sources */, + 07ECF5F72A2279940093C37B /* PostSessionFormRouter.swift in Sources */, 979F812229B842E200D6E964 /* UniversityNames.swift in Sources */, 0757552229FAA2AB008E73FB /* CircleAddButton.swift in Sources */, 0757552E29FAA2D6008E73FB /* PostSessionConfirmationScreen.swift in Sources */, 974D62D129FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift in Sources */, 0757553829FAA350008E73FB /* QuestionModel.swift in Sources */, - 0757553429FAA2D6008E73FB /* SessionConfirmationScreen.swift in Sources */, + 0757553429FAA2D6008E73FB /* ConfirmationScreen.swift in Sources */, 9760067C299FFCD2000945E2 /* SignUpPageView.swift in Sources */, 0793B41F2A19FA7200AF78C8 /* CustomNavView.swift in Sources */, 0793B4202A19FA7200AF78C8 /* CustomNavLink.swift in Sources */, diff --git a/ALUM/ALUM/Constants/DevelopmentModels.swift b/ALUM/ALUM/Constants/DevelopmentModels.swift index 497313f6..4fbcef7c 100644 --- a/ALUM/ALUM/Constants/DevelopmentModels.swift +++ b/ALUM/ALUM/Constants/DevelopmentModels.swift @@ -41,6 +41,41 @@ class DevelopmentModels { whyPaired: Optional("Modified Why Paired") ) + static var postSessionFormModel: [Question] = [ + Question( + question: "What topics did you discuss?", + type: "bullet", + id: "4cb4504d3308126edee7ef72b7e04a04db8a1f5d45f8137fcb2717a33515de8b", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ), + Question( + question: "Key takeaways from the session:", + type: "bullet", + id: "53f0ce23b3cb957455ded4c35a1fc7047b2365174b0cf05d2e945a31fde0d881", + answerBullet: ["Sasdlkfna;slkdfasdf;a"], + answerCheckboxBullet: [], + answerParagraph: "" + ), + Question( + question: "Next step(s):", + type: "bullet", + id: "6e4e9e6195735e254e25a9663977ccb51255717f0880726899788375b21e2c30", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ), + Question( + question: "Other notes:", + type: "text", + id: "fc58a4a3bfb853c240d3b9854695a7057d022a3b4dc1ec651cd0b9e2ef88ae8e", + answerBullet: [], + answerCheckboxBullet: [], + answerParagraph: "" + ) + ] + static var preSessionFormModel: [Question] = [ Question( question: "What topic(s) would you like to discuss?", diff --git a/ALUM/ALUM/ViewModels/QuestionViewModel.swift b/ALUM/ALUM/ViewModels/QuestionViewModel.swift index 227f6802..8fd8edcb 100644 --- a/ALUM/ALUM/ViewModels/QuestionViewModel.swift +++ b/ALUM/ALUM/ViewModels/QuestionViewModel.swift @@ -34,10 +34,7 @@ final class QuestionViewModel: ObservableObject { try await NotesService.shared.patchNotes(noteId: noteID, data: notesData) } - func fetchNotes(noteId: String) async throws { - DispatchQueue.main.async { - self.isLoading = true - } + func fetchNotes(noteId: String) async throws -> [Question] { let notesData: [QuestionGetData] = try await NotesService.shared.getNotes(noteId: noteId) var newQuestions: [Question] = [] for question in notesData { @@ -45,31 +42,34 @@ final class QuestionViewModel: ObservableObject { question.answer.toRaw(question: &questionToAdd) newQuestions.append(questionToAdd) } + return newQuestions + } + + func fetchPreSessionNotes(noteId: String) async throws { + DispatchQueue.main.async { + self.isLoading = true + } + let questions = try await self.fetchNotes(noteId: noteId) DispatchQueue.main.async { self.isLoading = false - self.questionList = newQuestions + self.questionList = questions } } - func loadPostNotes(notesID: String, otherNotesID: String) async throws { - var notesData: [QuestionGetData] = try await NotesService.shared.getNotes(noteId: notesID) - var notesDataOther: [QuestionGetData] = try await NotesService.shared.getNotes(noteId: otherNotesID) - for question in notesData { - var questionToAdd: Question = Question(question: question.question, - type: question.type, id: question.id) - question.answer.toRaw(question: &questionToAdd) - self.questionList.append(questionToAdd) + func fetchPostSessionNotes(notesId: String, otherNotesId: String) async throws { + DispatchQueue.main.async { + self.isLoading = true } - for question in notesDataOther { - var questionToAdd: Question = Question(question: question.question, - type: question.type, id: question.id) - question.answer.toRaw(question: &questionToAdd) - self.questionListOther.append(questionToAdd) + let primaryQuestions = try await self.fetchNotes(noteId: notesId) + let otherQuestions = try await self.fetchNotes(noteId: otherNotesId) + + DispatchQueue.main.async { + self.isLoading = false + self.questionList = primaryQuestions + self.questionListOther = otherQuestions } - self.isLoading = false - // TODO set currentIndex to 0 } - + func nextQuestion() { self.currentIndex += 1 if self.currentIndex == self.questionList.count - 1 { diff --git a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift b/ALUM/ALUM/Views/ConfirmationScreen.swift similarity index 96% rename from ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift rename to ALUM/ALUM/Views/ConfirmationScreen.swift index e2562807..3b45b34e 100644 --- a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/ConfirmationScreen.swift @@ -7,7 +7,7 @@ import SwiftUI -struct SessionConfirmationScreen: View { +struct ConfirmationScreen: View { @State var text: [String] // [primary text, subtext, button text] var body: some View { GeometryReader { geometry in @@ -67,7 +67,7 @@ struct SessionConfirmationTester: View { "Thank you for your feedback!", "Close"] var body: some View { - SessionConfirmationScreen(text: text) + ConfirmationScreen(text: text) } } diff --git a/ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift deleted file mode 100644 index 1223581c..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// MissedSessionScreen.swift -// ALUM -// -// Created by Jenny Mar on 4/5/23. -// - -import SwiftUI - -struct MissedSessionScreen: View { - enum OptionType { - case cancelled - case emergency - case forgot - case notShowed - case other - case notSay - } - - @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var validated: Bool = true - @State var selectedOption: OptionType? - @State var noOption: Bool = false - @State var otherEmpty: Bool = false - @State var otherText: String = "" - @State var otherUser: String = "Mentor" - @State var date: String = "date" - @State var time: String = "time" - - var body: some View { - GeometryReader { geometry in - ZStack { - Color("ALUM White 2").edgesIgnoringSafeArea(.all) - VStack { - ScrollView { - VStack { - ZStack { - RoundedRectangle(cornerRadius: 12) - .fill(Color("ALUM Light Blue")) - Text("You have successfully booked a session with \(otherUser) on \(date) at \(time).") - .font(.custom("Metropolis-Regular", size: 17)) - .lineSpacing(10) - .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) - } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 32) - .padding(.top, 8) - - HStack { - Text("What went wrong?") - .font(.custom("Metropolis-Regular", size: 17)) - .foregroundColor(Color("ALUM Dark Blue")) - .padding(.init(top: 0, leading: 16, bottom: 3, trailing: 16)) - Spacer() - } - if noOption == true { - HStack { - Text("Please select an option") - .font(.custom("Metropolis-Regular", size: 13)) - .foregroundColor(Color("FunctionalError")) - .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) - Spacer() - } - } else if otherEmpty == true { - HStack { - Text("Please fill in Other") - .font(.custom("Metropolis-Regular", size: 13)) - .foregroundColor(Color("FunctionalError")) - .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) - Spacer() - } - } - - MultipleChoice(content: "Mentor/ee and I decided to cancel", - checked: selectedOption == .cancelled, otherText: $otherText) - .onTapGesture {selectedOption = .cancelled - viewModel.missedOption = "Mentor/ee and I decided to cancel" - noOption = false; otherEmpty = false} - .padding(.bottom, 12) - MultipleChoice(content: "I had an emergency", - checked: selectedOption == .emergency, - otherText: $otherText) - .onTapGesture {selectedOption = .emergency - viewModel.missedOption = "I had an emergency" - noOption = false; otherEmpty = false} - .padding(.bottom, 12) - MultipleChoice(content: "I forgot about the session", - checked: selectedOption == .forgot, otherText: $otherText) - .onTapGesture {selectedOption = .forgot - viewModel.missedOption = "I forgot about the session" - noOption = false; otherEmpty = false} - .padding(.bottom, 12) - MultipleChoice(content: "Mentor/ee didn't show", - checked: selectedOption == .notShowed, otherText: $otherText) - .onTapGesture {selectedOption = .notShowed - viewModel.missedOption = "Mentor/ee didn't show" - noOption = false; otherEmpty = false} - .padding(.bottom, 12) - MultipleChoice(content: "Other:", - checked: selectedOption == .other, otherText: $otherText) - .onTapGesture {selectedOption = .other - viewModel.missedOption = otherText - noOption = false} - .padding(.bottom, 12) - MultipleChoice(content: "Prefer not to say", - checked: selectedOption == .notSay, otherText: $otherText) - .onTapGesture {selectedOption = .notSay - viewModel.missedOption = "Prefer not to say" - noOption = false; otherEmpty = false} - .padding(.bottom, 12) - - } - } - ZStack { - Rectangle() - .frame(maxWidth: .infinity) - .frame(height: 114) - .foregroundColor(Color.white) - .ignoresSafeArea(edges: .all) - if viewModel.missedOption != "" { - NavigationLink(destination: SessionConfirmationScreen( - text: ["Missed session form submitted!", - "Thank you for your feedback!", "Close"]), label: { - HStack { - Text("Submit") - .font(.custom("Metropolis-Regular", size: 17)) - } - }) - .buttonStyle(FilledInButtonStyle(disabled: false)) - .padding(.bottom, 32) - .frame(width: geometry.size.width * 0.92) - } else { - if selectedOption == .other { - - } else { - - } - Button("Submit") { - if selectedOption == .other { - otherEmpty = true - } else { - noOption = true - } - } - .font(.custom("Metropolis-Regular", size: 17)) - .buttonStyle(FilledInButtonStyle(disabled: true)) - .padding(.bottom, 32) - .frame(width: geometry.size.width * 0.92) - } - - } - .padding(.leading, 16) - .padding(.trailing, 16) - .frame(alignment: .bottom) - .frame(maxWidth: .infinity) - .background(Color.white.ignoresSafeArea(edges: .bottom)) - - } - .onAppear { - if viewModel.missedOption == "Mentor/ee and I decided to cancel" { - selectedOption = .cancelled - } else if viewModel.missedOption == "I had an emergency" { - selectedOption = .emergency - } else if viewModel.missedOption == "I forgot about the session" { - selectedOption = .forgot - } else if viewModel.missedOption == "Other" { - selectedOption = .other - } else if viewModel.missedOption == "Prefer not to say" { - selectedOption = .notSay - } - } - } - .navigationBarItems(leading: NavigationLink( - destination: PostSessionQuestionScreen(viewModel: viewModel), - label: { - HStack { - Image(systemName: "chevron.left") - Text("Cancel") - .font(.custom("Metropolis-Regular", size: 13)) - } - } - )) - .navigationBarTitle(Text("Missed Session").font(.custom("Metropolis-Regular", size: 17)), - displayMode: .inline) - .navigationBarBackButtonHidden() - } - } -} - -struct MissedSessionScreen_Previews: PreviewProvider { - static private var viewModel = QuestionViewModel() - - static var previews: some View { - MissedSessionScreen(viewModel: viewModel) - } -} diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift deleted file mode 100644 index 86c952b3..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift +++ /dev/null @@ -1,203 +0,0 @@ -// -// PostSessionConfirmationScreen.swift -// ALUM -// -// Created by Jenny Mar on 4/11/23. -// - -import SwiftUI - -struct PostSessionConfirmationScreen: View { - @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var notesID: String = "" - @State var currNotes: String = "this" // "this" or "other" - - func setMyNotes() { - currNotes = "this" - } - - func setOtherNotes() { - currNotes = "other" - } - - var body: some View { - VStack { - StaticProgressBarComponent(nodes: viewModel.questionList.count, - filledNodes: viewModel.questionList.count, activeNode: 0) - .background(Color.white) - - ScrollView { - content - } - footer - .padding(.horizontal, 16) - .padding(.top, 32) - .padding(.bottom, 40) - .background(Rectangle().fill(Color.white).shadow(radius: 8)) - } - .edgesIgnoringSafeArea(.bottom) - .applyPostSessionScreenHeaderModifier() - } - - var footer: some View { - HStack { - Button { - dismiss() - } label: { - HStack { - Image(systemName: "arrow.left") - Text("Back") - } - } - .buttonStyle(OutlinedButtonStyle()) - - Spacer() - - Button { - Task { - do { - // (todo) remove hardcoding - try await viewModel.submitNotesPatch(noteID: notesID) - self.viewModel.submitSuccess = true - } catch { - print("Error") - } - } - } label: { - Text("Save") - } - .buttonStyle(FilledInButtonStyle()) - NavigationLink(destination: SessionConfirmationScreen( - text: ["Post-session form saved!", - "You can continue on the notes later under \"Sessions\".", "Great"]), - isActive: $viewModel.submitSuccess) { - EmptyView() - } - } - } - - var content: some View { - VStack { - HStack { - Text("Summary") - .font(.custom("Metropolis-Regular", size: 34)) - .foregroundColor(Color("NeutralGray3")) - Spacer() - } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 32) - .padding(.top, 8) - - HStack { - Button { - setMyNotes() - } label: { - if currNotes == "this" { - Text("MY NOTES") - .font(.custom("Metropolis-Regular", size: 16)) - .foregroundColor(Color("ALUM Dark Blue")) - .bold() - } else { - Text("MY NOTES") - .font(.custom("Metropolis-Regular", size: 16)) - .foregroundColor(Color("ALUM Dark Blue")) - } - - } - .padding(.trailing, 40) - Button { - setOtherNotes() - } label: { - if currNotes == "other" { - Text((viewModel.currentUser.role == UserRole.mentee) ? "MENTOR NOTES" : "MENTEE NOTES") - .font(.custom("Metropolis-Regular", size: 16)) - .foregroundColor(Color("ALUM Dark Blue")) - .bold() - } else { - Text((viewModel.currentUser.role == UserRole.mentee) ? "MENTOR NOTES" : "MENTEE NOTES") - .font(.custom("Metropolis-Regular", size: 16)) - .foregroundColor(Color("ALUM Dark Blue")) - } - } - Spacer() - } - .padding(.leading, 16) - ZStack { - Divider() - .background(Color("ALUM Light Blue")) - .frame(width: 358) - .padding(.bottom, 32) - if currNotes == "this" { - Divider() - .background(Color("ALUM Dark Blue").frame(height: 3)) - .frame(width: 84) - .padding(.bottom, 32) - .padding(.trailing, 275) - } else { - Divider() - .background(Color("ALUM Dark Blue").frame(height: 3)) - .frame(width: 129) - .padding(.bottom, 32) - .padding(.leading, 35) - } - } - - ForEach((currNotes == "this") ? viewModel.questionList : viewModel.questionListOther, - id: \.self) { currQuestion in - HStack { - Text(currQuestion.question) - .foregroundColor(Color("ALUM Dark Blue")) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 8) - - if currQuestion.type == "text" { - HStack { - Text(currQuestion.answerParagraph) - .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 32) - } else if currQuestion.type == "bullet" { - VStack { - ForEach(currQuestion.answerBullet, id: \.self) { item in - HStack { - Image(systemName: "circle.fill") - .foregroundColor(Color.black) - .font(.system(size: 5.0)) - Text(item) - .font(Font.custom("Metropolis-Regular", - size: 17, - relativeTo: .headline)) - .foregroundColor(.black) - .padding(.bottom, 2) - .lineSpacing(4.0) - Spacer() - } - .padding(.leading, 16) - .padding(.trailing, 16) - } - } - .padding(.bottom, 32) - } - } - } - } -} - -struct PostSessionConfirmationScreen_Previews: PreviewProvider { - static private var viewModel = QuestionViewModel() - - static var previews: some View { - PostSessionConfirmationScreen(viewModel: viewModel) - } -} diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift deleted file mode 100644 index b5e7e41e..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionQuestionScreen.swift +++ /dev/null @@ -1,183 +0,0 @@ -// -// MenteePostSessionQuestionScreen.swift -// ALUM -// -// Created by Jenny Mar on 4/5/23. -// - -import SwiftUI - -struct PostSessionQuestionScreen: View { - - @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var notesID: String = "" - @State var otherUser: String = "Mentor" - @State var date: String = "date" - @State var time: String = "time" - - var body: some View { - VStack { - DynamicProgressBarComponent(nodes: $viewModel.questionList.count, - filledNodes: $viewModel.currentIndex, activeNode: $viewModel.currentIndex) - .padding() - .background(Color.white) - - ScrollView { - content - } - - footer - .padding(.horizontal, 16) - .padding(.top, 32) - .padding(.bottom, 40) - .background(Rectangle().fill(Color.white).shadow(radius: 8)) - - } - .edgesIgnoringSafeArea(.bottom) - } - - @ViewBuilder - var footer: some View { - if !viewModel.lastQuestion { - if viewModel.currentIndex == 0 { - Button { - viewModel.nextQuestion() - } label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } - .buttonStyle(FilledInButtonStyle()) - } else { - HStack { - Button { - viewModel.prevQuestion() - } label: { - HStack { - Image(systemName: "arrow.left") - Text("Back") - } - } - .buttonStyle(OutlinedButtonStyle()) - - Spacer() - - Button { - viewModel.nextQuestion() - } label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } - .buttonStyle(FilledInButtonStyle()) - } - } - } else { - HStack { - Button { - viewModel.prevQuestion() - } label: { - HStack { - Image(systemName: "arrow.left") - Text("Back") - } - } - .buttonStyle(OutlinedButtonStyle()) - - Spacer() - - NavigationLink( - destination: PostSessionConfirmationScreen(viewModel: viewModel, notesID: notesID), - label: { - HStack { - Text("Continue") - .font(.custom("Metropolis-Regular", size: 17)) - Image(systemName: "arrow.right") - .font(.system(size: 17)) - .foregroundColor(Color.white) - } - } - ) - .buttonStyle(FilledInButtonStyle()) - } - } - } - - var content: some View { - VStack { - if viewModel.currentIndex == 0 { - ZStack { - RoundedRectangle(cornerRadius: 12) - .fill(Color("ALUM Light Blue")) - VStack { - Text("Let's reflect on your session with \(otherUser) on \(date) at \(time).") - .font(.custom("Metropolis-Regular", size: 17)) - .lineSpacing(10) - .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) - Spacer() - NavigationLink(destination: MissedSessionScreen(viewModel: viewModel), label: { - HStack { - Text("Session didn't happen?") - .foregroundColor(Color("ALUM Dark Blue")) - .underline() - .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) - Spacer() - } - }) - } - } - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.bottom, 32) - .padding(.top, 8) - } - - if viewModel.questionList[viewModel.currentIndex].type == "text" { - ParagraphInput(question: viewModel.questionList[viewModel.currentIndex].question, - text: $viewModel.questionList[viewModel.currentIndex].answerParagraph) - .padding(.leading, 16) - .padding(.trailing, 16) - .padding(.top, 8) - } else if viewModel.questionList[viewModel.currentIndex].type == "bullet" { - Text(viewModel.questionList[viewModel.currentIndex].question) - .foregroundColor(Color("ALUM Dark Blue")) - .font(Font.custom("Metropolis-Regular", size: 17)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.bottom, 16) - .padding(.top, 8) - BulletsView(bullets: $viewModel.questionList[viewModel.currentIndex].answerBullet, - question: viewModel.questionList[viewModel.currentIndex].question) - } else if viewModel.questionList[viewModel.currentIndex].type == "checkbox-bullet" { - Text(viewModel.questionList[viewModel.currentIndex].question) - .foregroundColor(Color("ALUM Dark Blue")) - .font(Font.custom("Metropolis-Regular", size: 17)) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .padding(.bottom, 16) - .padding(.top, 8) - CheckboxBulletsView( - checkboxBullets: - $viewModel.questionList[viewModel.currentIndex].answerCheckboxBullet, - question: viewModel.questionList[viewModel.currentIndex].question) - } - } - } -} - -struct PostSessionQuestionScreen_Previews: PreviewProvider { - static private var viewModel = QuestionViewModel() - - static var previews: some View { - PostSessionQuestionScreen(viewModel: viewModel) - } -} diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift b/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift deleted file mode 100644 index f565bbf0..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// PostSessionView.swift -// ALUM -// -// Created by Jenny Mar on 4/5/23. -// - -import SwiftUI - -struct PostSessionScreenHeaderModifier: ViewModifier { - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "XXX", - backDestination: Text("TODO Blank"), - title: "Post-session Notes", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyPostSessionScreenHeaderModifier() -> some View { - self.modifier(PostSessionScreenHeaderModifier()) - } -} - -struct PostSessionView: View { - @StateObject private var viewModel = QuestionViewModel() - - @State var notesID: String = "" - @State var otherNotesID: String = "" - - @State var otherName: String = "" - @State var date: String = "" - @State var time: String = "" - - var body: some View { - Group { - if !viewModel.isLoading { - PostSessionQuestionScreen( - viewModel: viewModel, - notesID: notesID, - otherUser: otherName, - date: date, - time: time - ) - .navigationBarTitle("", displayMode: .inline) - .navigationBarHidden(true) - } else { - Text("Loading...") - .navigationBarTitle("") - .navigationBarHidden(true) - .onAppear { - Task { - do { - try await viewModel.loadPostNotes(notesID: notesID, otherNotesID: otherNotesID) - } catch { - print("Error") - } - } - } - } - } - } -} - -struct PostSessionView_Previews: PreviewProvider { - static var previews: some View { - PostSessionView() - } -} diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift index 352c4392..ade74ef5 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift @@ -55,7 +55,7 @@ struct PreSessionConfirmationScreen: View { } .buttonStyle(FilledInButtonStyle()) // TODO change this to custom nav link - NavigationLink(destination: SessionConfirmationScreen( + NavigationLink(destination: ConfirmationScreen( text: ["Pre-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), isActive: $viewModel.submitSuccess) { @@ -130,6 +130,6 @@ struct PreSessionConfirmationScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - PreSessionConfirmationScreen(viewModel: viewModel, notesID: "6464276b6f05d9703f069761") + PreSessionConfirmationScreen(viewModel: viewModel, notesID: "646a6e164082520f4fcf2f92") } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift index af9a983a..89d18e7e 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift @@ -32,7 +32,7 @@ struct PreSessionFormRouter: View { .onAppear { Task { do { - try await viewModel.fetchNotes(noteId: notesID) + try await viewModel.fetchPreSessionNotes(noteId: notesID) } catch { print("ERROR PreSessionFormRouter \(error)") } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift index 17f04633..61414783 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift @@ -34,7 +34,7 @@ struct PreSessionQuestionScreen: View { } let currentQuestion = viewModel.questionList[currentIndex] - print("currentQuestion", currentQuestion) + return VStack { if viewModel.currentIndex == 0 { firstQuestionBanner diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift deleted file mode 100644 index a5978565..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// PreSessionView.swift -// ALUM -// -// Created by Neelam Gurnani on 3/9/23. -// - -import SwiftUI - -struct PreSessionView: View { - - @StateObject private var viewModel = QuestionViewModel() - - @State var notesID: String = "" - - @State var otherName: String = "" - @State var date: String = "" - @State var time: String = "" - - var body: some View { - content - .customNavBarItems( - title: "\(date) Pre-session Notes", - isPurple: false, - backButtonHidden: false - ) - } - var content: some View { - return - Group { - if !viewModel.isLoading { - PreSessionQuestionScreen( - viewModel: viewModel, - otherUser: otherName, - date: date, - time: time - ) - } else { - Text("Loading...") - .navigationBarTitle("") - .navigationBarHidden(true) - .onAppear { - Task { - do { - try await viewModel.fetchNotes(noteId: notesID) - } catch { - print("Error") - } - } - // viewModel.loadTestData() - } - } - } - // .navigationBarBackButtonHidden() - - } -} - -struct PreSessionView_Previews: PreviewProvider { - static var previews: some View { - PreSessionView() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift index 27d6d33c..de789c86 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift @@ -169,7 +169,7 @@ struct SessionDetailsScreen: View { otherNoteId = session.postSessionMentor! otherName = session.mentorName case .mentor: - formIsComplete = session.postSessionMenteeCompleted + formIsComplete = session.postSessionMentorCompleted editableNoteId = session.postSessionMentor! otherNoteId = session.postSessionMentee! otherName = session.menteeName @@ -178,7 +178,6 @@ struct SessionDetailsScreen: View { editableNoteId = "" otherNoteId = "" otherName = "" - // TODO Internal error } return Group { @@ -199,11 +198,11 @@ struct SessionDetailsScreen: View { if !formIsComplete { CustomNavLink( - destination: PostSessionView( + destination: PostSessionFormRouter( notesID: editableNoteId, - otherNotesID: otherNoteId, - otherName: otherName, - date: session.dateShortHandString, + otherNotesId: otherNoteId, + otherName: otherName, + date: session.dateShortHandString, time: session.startTimeString ), label: { diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift b/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift index 7e24939a..6cb95f69 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift @@ -7,39 +7,17 @@ import SwiftUI -struct ViewPostSessionNotesModifier: ViewModifier { - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Post-session Notes", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyViewPostSessionNotesModifier() -> some View { - self.modifier(ViewPostSessionNotesModifier()) - } -} - struct ViewPostSessionNotesPage: View { @StateObject var viewModel = QuestionViewModel() - @State var currNotes: String = "this" // "this" or "other" - @State var notesID: String = "" - @State var otherNotesID: String = "" + var notesID: String + var otherNotesID: String + var otherName: String + var date: String + var time: String + + @State var currNotes: String = "this" // "this" or "other" - @State var otherName: String = "" - @State var date: String = "" - @State var time: String = "" func setMyNotes() { currNotes = "this" @@ -50,30 +28,24 @@ struct ViewPostSessionNotesPage: View { } var body: some View { + loadingAbstraction + .customNavBarItems(title: "\(date) Post-session Notes", isPurple: false, backButtonHidden: false) + } + var loadingAbstraction: some View { Group { if !viewModel.isLoading { - VStack { - ScrollView { - content - } - if currNotes == "this" { - footer - .padding(.horizontal, 16) - .padding(.top, 32) - .padding(.bottom, 40) - .background(Rectangle().fill(Color.white).shadow(radius: 8)) - } - } - .edgesIgnoringSafeArea(.bottom) - .applyViewPostSessionNotesModifier() + loadedView } else { - ProgressView() + LoadingView(text: "ViewPostSessionNotesPage") } } .onAppear { Task { do { - try await viewModel.loadPostNotes(notesID: notesID, otherNotesID: otherNotesID) + try await viewModel.fetchPostSessionNotes( + notesId: notesID, + otherNotesId: otherNotesID + ) } catch { print("Error") } @@ -81,23 +53,38 @@ struct ViewPostSessionNotesPage: View { } } + var loadedView: some View { + VStack { + ScrollView { + content + } + if currNotes == "this" { + footer + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 40) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) + } + } + .edgesIgnoringSafeArea(.bottom) + } var footer: some View { - HStack { - NavigationLink { - PostSessionQuestionScreen( - viewModel: viewModel, - notesID: notesID, - otherUser: otherName, - date: date, time: time - ) - } label: { + return CustomNavLink ( + destination: + PostSessionFormRouter( + notesID: notesID, + otherNotesId: otherNotesID, + otherName: otherName, + date: date, + time: time + ), + label: { HStack { - Image(systemName: "pencil.line") - Text("Edit") - } - } + Image(systemName: "pencil.line") + Text("Edit") + } + }) .buttonStyle(FilledInButtonStyle()) - } } var content: some View { @@ -219,6 +206,6 @@ struct ViewPostSessionNotesPage: View { struct ViewPostSessionNotesPage_Previews: PreviewProvider { static var previews: some View { - ViewPostSessionNotesPage() + ViewPostSessionNotesPage(notesID: "646c8ea5004999f332c55f84", otherNotesID: "646c8ea5004999f332c55f86", otherName: "Mentor", date: "5/23", time: "9:30 AM") } } diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift index dedd7cd1..cc686289 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift +++ b/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift @@ -32,7 +32,7 @@ struct ViewPreSessionNotesPage: View { .onAppear { Task { do { - try await viewModel.fetchNotes(noteId: notesID) + try await viewModel.fetchPreSessionNotes(noteId: notesID) } catch { print("Error") } diff --git a/backend/src/routes/notes.ts b/backend/src/routes/notes.ts index adbf4778..b62511e7 100644 --- a/backend/src/routes/notes.ts +++ b/backend/src/routes/notes.ts @@ -22,6 +22,7 @@ router.get("/notes/:id", async (req: Request, res: Response, next: NextFunction) try { const id = req.params.id; const note = await Note.findById(id); + console.log(`GETTING note - ID ${note?.id}`) if (note == null) throw ServiceError.NOTE_WAS_NOT_FOUND; if (note.type === "post") { const temp = await Session.findById(note.session); From 25bd7bdda0452d0b640c6c9cef36bfaca4f9b208 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 14:26:14 -0700 Subject: [PATCH 17/33] hide back button when user navigates back to home tab after confirmation screen --- .../Components/CustomNavBar/CustomNavBarContainerView.swift | 6 +++++- ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index bcca8f33..bfbfd7d3 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -9,7 +9,11 @@ import SwiftUI struct CustomNavBarContainerView: View { let content: Content - @State private var showBackButton: Bool = false + + // By default, we show the back button unless some view explicitly sets + // this to false + @State private var showBackButton: Bool = true + @State private var title: String = "" @State private var isPurple: Bool = false @State private var isHidden: Bool = false diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift index 43d3e761..4b5eebba 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -19,7 +19,6 @@ struct CustomNavLink: View { destination: CustomNavBarContainerView(content: { destination - .customNavigationBarBackButtonHidden(false) }) .navigationBarHidden(true) From 6ea96121515dd72d0ad443a4d47a17453c065b92 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 14:29:25 -0700 Subject: [PATCH 18/33] move view notes screens into pre and post form folders --- ALUM/ALUM.xcodeproj/project.pbxproj | 14 +++----------- .../ViewPostSessionNotesPage.swift | 0 .../ViewPreSessionNotesPage.swift | 0 .../SessionDetailsScreen.swift | 0 4 files changed, 3 insertions(+), 11 deletions(-) rename ALUM/ALUM/Views/{SessionDetailsView => PostSessionForm}/ViewPostSessionNotesPage.swift (100%) rename ALUM/ALUM/Views/{SessionDetailsView => PreSessionForm}/ViewPreSessionNotesPage.swift (100%) rename ALUM/ALUM/Views/{SessionDetailsView => }/SessionDetailsScreen.swift (100%) diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index dca7e350..14af2736 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -357,6 +357,7 @@ 07ECF5F82A22A94A0093C37B /* PostSessionForm */ = { isa = PBXGroup; children = ( + 974D62D029FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift */, 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */, 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */, 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */, @@ -420,8 +421,8 @@ 07F9A50E297180F000BC11A8 /* Views */ = { isa = PBXGroup; children = ( + 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */, 07ECF5F82A22A94A0093C37B /* PostSessionForm */, - 979303D029E7EA680053C30E /* SessionDetailsView */, 07E885342A19F0B800B7AD27 /* Routers */, 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */, 97A31AC729BAFA7600B79D30 /* PreSessionForm */, @@ -465,16 +466,6 @@ path = Services; sourceTree = ""; }; - 979303D029E7EA680053C30E /* SessionDetailsView */ = { - isa = PBXGroup; - children = ( - 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */, - 974D62D029FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift */, - 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, - ); - path = SessionDetailsView; - sourceTree = ""; - }; 97992B5D29A6E7E200701CC7 /* SignUpPage */ = { isa = PBXGroup; children = ( @@ -492,6 +483,7 @@ 97A31AC729BAFA7600B79D30 /* PreSessionForm */ = { isa = PBXGroup; children = ( + 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, 0757552829FAA2D5008E73FB /* PreSessionConfirmationScreen.swift */, 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */, 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */, diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift b/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift similarity index 100% rename from ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift rename to ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift similarity index 100% rename from ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift rename to ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift similarity index 100% rename from ALUM/ALUM/Views/SessionDetailsView/SessionDetailsScreen.swift rename to ALUM/ALUM/Views/SessionDetailsScreen.swift From 66b61a6cde262dd197544a822a0701a46c22d041 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 15:00:51 -0700 Subject: [PATCH 19/33] move preference key defaults to common place so that its consistent between CustomNavBarContainerView and PreferenceKey --- .../CustomNavBarContainerView.swift | 17 +++++++++++++---- .../CustomNavBarPreferenceKeys.swift | 8 ++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index bfbfd7d3..06b4e926 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -7,20 +7,29 @@ import SwiftUI +struct CustomNavBarDefaultValues { + static var showBackButton: Bool = true + static var title: String = "" + static var barIsPurple: Bool = false + static var barIsHidden: Bool = false +} + struct CustomNavBarContainerView: View { let content: Content // By default, we show the back button unless some view explicitly sets // this to false - @State private var showBackButton: Bool = true + @State private var showBackButton: Bool = CustomNavBarDefaultValues.showBackButton + + @State private var title: String = CustomNavBarDefaultValues.title + @State private var isPurple: Bool = CustomNavBarDefaultValues.barIsPurple + @State private var isHidden: Bool = CustomNavBarDefaultValues.barIsHidden - @State private var title: String = "" - @State private var isPurple: Bool = false - @State private var isHidden: Bool = false init(@ViewBuilder content: () -> Content) { self.content = content() } + var body: some View { return VStack(spacing: 0) { if !isHidden { diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift index 0f2ad1b3..67b06375 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI struct CustomNavBarTitlePreferenceKey: PreferenceKey { - static var defaultValue: String = "" + static var defaultValue: String = CustomNavBarDefaultValues.title static func reduce(value: inout String, nextValue: () -> String) { value = nextValue() @@ -17,7 +17,7 @@ struct CustomNavBarTitlePreferenceKey: PreferenceKey { } struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { - static var defaultValue: Bool = false + static var defaultValue: Bool = CustomNavBarDefaultValues.barIsHidden static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() @@ -25,7 +25,7 @@ struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { } struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { - static var defaultValue: Bool = false + static var defaultValue: Bool = CustomNavBarDefaultValues.barIsPurple static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() @@ -33,7 +33,7 @@ struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { } struct CustomNavBarBackButtonHiddenPreferenceKey: PreferenceKey { - static var defaultValue: Bool = true + static var defaultValue: Bool = !CustomNavBarDefaultValues.showBackButton static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() From 4f0eab830d4787e3611349991c06fa31da25c2fc Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 15:30:34 -0700 Subject: [PATCH 20/33] fix issue where clicking HorizontalMenteeCard wouldn't navigate --- .../Components/HorizontalMenteeCard.swift | 81 ++++++++++--------- ALUM/ALUM/Components/MentorCard.swift | 6 +- ALUM/ALUM/Views/SessionDetailsScreen.swift | 7 +- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/ALUM/ALUM/Components/HorizontalMenteeCard.swift b/ALUM/ALUM/Components/HorizontalMenteeCard.swift index e488c01f..5f8c9e1a 100644 --- a/ALUM/ALUM/Components/HorizontalMenteeCard.swift +++ b/ALUM/ALUM/Components/HorizontalMenteeCard.swift @@ -39,47 +39,44 @@ struct HorizontalMenteeCard: View { var loadedView: some View { let mentee = viewModel.mentee! - return Button { - } label: { - ZStack { - RoundedRectangle(cornerRadius: 12.0) - .frame(width: 358, height: 118) - .foregroundColor(Color("ALUM Primary Purple")) - if isEmpty { - Circle() - .frame(width: 85) - .foregroundColor(Color("NeutralGray1")) - .offset(x: -112.5) - } else { - Image(mentee.imageId) - .resizable() - .clipShape(Circle()) - .frame(width: 85, height: 85) - .offset(x: -112.5) + return ZStack { + RoundedRectangle(cornerRadius: 12.0) + .frame(width: 358, height: 118) + .foregroundColor(Color("ALUM Primary Purple")) + if isEmpty { + Circle() + .frame(width: 85) + .foregroundColor(Color("NeutralGray1")) + .offset(x: -112.5) + } else { + Image(mentee.imageId) + .resizable() + .clipShape(Circle()) + .frame(width: 85, height: 85) + .offset(x: -112.5) + } + VStack { + HStack { + Text(mentee.name) + .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(.white) + Spacer() } - VStack { - HStack { - Text(mentee.name) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(.white) - Spacer() - } - .padding(.bottom, 4) - .offset(x: 149) - HStack { - Image(systemName: "graduationcap") - .resizable() - .frame(width: 19, height: 18) - .foregroundColor(.white) - Text(String(mentee.grade) + "th Grade" + " @ " + school) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(.white) - .frame(width: 200, alignment: .leading) - Spacer() - } - .offset(x: 150) - .padding(.bottom, 4) + .padding(.bottom, 4) + .offset(x: 149) + HStack { + Image(systemName: "graduationcap") + .resizable() + .frame(width: 19, height: 18) + .foregroundColor(.white) + Text(String(mentee.grade) + "th Grade" + " @ " + school) + .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + .foregroundColor(.white) + .frame(width: 200, alignment: .leading) + Spacer() } + .offset(x: 150) + .padding(.bottom, 4) } } } @@ -87,6 +84,10 @@ struct HorizontalMenteeCard: View { struct HorizontalMenteeCard_Previews: PreviewProvider { static var previews: some View { - HorizontalMenteeCard(menteeId: "6431b99ebcf4420fe9825fe3") + Button(action: { + print("print") + }, label: { + HorizontalMenteeCard(menteeId: "6431b99ebcf4420fe9825fe3") + }) } } diff --git a/ALUM/ALUM/Components/MentorCard.swift b/ALUM/ALUM/Components/MentorCard.swift index c8dd2d33..be379cfe 100644 --- a/ALUM/ALUM/Components/MentorCard.swift +++ b/ALUM/ALUM/Components/MentorCard.swift @@ -87,6 +87,10 @@ struct MentorCard: View { struct MentorCard_Previews: PreviewProvider { static var previews: some View { - MentorCard(isEmpty: true) + Button(action: { + print("print") + }, label: { + MentorCard(uID: "6431b9a2bcf4420fe9825fe5") + }) } } diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index de789c86..cba03d24 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -256,7 +256,12 @@ extension SessionDetailsScreen { } .padding(.bottom, 5) - CustomNavLink(destination: MenteeProfileScreen(uID: session.menteeId).customNavigationTitle("Mentee Profile")) { + CustomNavLink( + destination: + MenteeProfileScreen( + uID: session.menteeId + ).customNavigationTitle("Mentee Profile") + ) { HorizontalMenteeCard( menteeId: session.menteeId, isEmpty: true From 52618a80fd7f8f5505d9fc589645e1956f6d74ef Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 23 Apr 2023 12:41:47 -0700 Subject: [PATCH 21/33] Add App Icon in Assets --- .../ALUM app icon (120x120) 1.png | Bin 0 -> 9254 bytes .../ALUM app icon (120x120).png | Bin 0 -> 9254 bytes .../ALUM app icon (180x180).png | Bin 0 -> 15907 bytes .../ALUM app icon (40x40).png | Bin 0 -> 2421 bytes .../ALUM app icon (58x58).png | Bin 0 -> 3672 bytes .../ALUM app icon (60x60).png | Bin 0 -> 3761 bytes .../ALUM app icon (80x80).png | Bin 0 -> 5444 bytes .../ALUM app icon (87x87).png | Bin 0 -> 6150 bytes .../AppIcon.appiconset/Contents.json | 8 ++++++++ 9 files changed, 8 insertions(+) create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120) 1.png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (180x180).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (40x40).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (58x58).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (60x60).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (80x80).png create mode 100644 ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (87x87).png diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120) 1.png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120) 1.png new file mode 100644 index 0000000000000000000000000000000000000000..c06314f0805e4db77ddf98595f29b34a57be0b0b GIT binary patch literal 9254 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120).png new file mode 100644 index 0000000000000000000000000000000000000000..c06314f0805e4db77ddf98595f29b34a57be0b0b GIT binary patch literal 9254 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (180x180).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (180x180).png new file mode 100644 index 0000000000000000000000000000000000000000..6168e995e2d3da6e972358ace164373b84edcfc1 GIT binary patch literal 15907 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 K#cM>3LjDI1JOujy literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (40x40).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (40x40).png new file mode 100644 index 0000000000000000000000000000000000000000..f7e4c953df1434298d9d5cd9073dcbb1f93bb28e GIT binary patch literal 2421 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (58x58).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (58x58).png new file mode 100644 index 0000000000000000000000000000000000000000..1828b0d0c5a155025c34f557ef95441252b27dd6 GIT binary patch literal 3672 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (60x60).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (60x60).png new file mode 100644 index 0000000000000000000000000000000000000000..62157719e65d4d809fbda0746b6cdb9a1fdada44 GIT binary patch literal 3761 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (80x80).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (80x80).png new file mode 100644 index 0000000000000000000000000000000000000000..4f3138a084a4796a2ec2d67d80789a700760947f GIT binary patch literal 5444 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 literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json index 9221b9bb..da2b46f1 100644 --- a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,41 +1,49 @@ { "images" : [ { + "filename" : "ALUM app icon (40x40).png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { + "filename" : "ALUM app icon (60x60).png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { + "filename" : "ALUM app icon (58x58).png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { + "filename" : "ALUM app icon (87x87).png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { + "filename" : "ALUM app icon (80x80).png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { + "filename" : "ALUM app icon (120x120).png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { + "filename" : "ALUM app icon (120x120) 1.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { + "filename" : "ALUM app icon (180x180).png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" From 356a8732d62734d5df4eb8c258d331f732073778 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 20:40:53 -0700 Subject: [PATCH 22/33] force light mode despite user's settings --- ALUM/ALUM/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ALUM/ALUM/Info.plist b/ALUM/ALUM/Info.plist index e9b3de4f..ab6a4b1a 100644 --- a/ALUM/ALUM/Info.plist +++ b/ALUM/ALUM/Info.plist @@ -9,5 +9,7 @@ Metropolis-ExtraBoldItalic.otf Metropolis-Thin.otf + UIUserInterfaceStyle + Light From c6524229c6a4ebd1a7f1fd4cda122f8ded5c91bd Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 20:59:08 -0700 Subject: [PATCH 23/33] add missing post session form files and home screen --- ALUM/ALUM/Views/HomeScreen.swift | 100 +++++++++ .../PostSessionForm/MissedSessionScreen.swift | 199 ++++++++++++++++++ .../PostSessionConfirmationScreen.swift | 199 ++++++++++++++++++ .../PostSessionFormRouter.swift | 79 +++++++ .../PostSessionQuestionScreen.swift | 165 +++++++++++++++ .../PostSessionForm/PostSessionView.swift | 76 +++++++ 6 files changed, 818 insertions(+) create mode 100644 ALUM/ALUM/Views/HomeScreen.swift create mode 100644 ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift create mode 100644 ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift create mode 100644 ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift create mode 100644 ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift create mode 100644 ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift diff --git a/ALUM/ALUM/Views/HomeScreen.swift b/ALUM/ALUM/Views/HomeScreen.swift new file mode 100644 index 00000000..832d65f9 --- /dev/null +++ b/ALUM/ALUM/Views/HomeScreen.swift @@ -0,0 +1,100 @@ +// +// HomeScreen.swift +// ALUM +// +// Created by Aman Aggarwal on 5/22/23. +// + +import SwiftUI + +struct HomeScreen: View { + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared + @State public var showCalendlyWebView = false + + var body: some View { + loadedView + .background(ALUMColor.beige.color) + .customNavigationIsPurple(false) + .customNavigationBarBackButtonHidden(true) + } + + + var loadedView: some View { + return VStack { + if currentUser.role == .mentee { + menteeView + .customNavigationTitle("Book session with mentor") + } else if currentUser.role == .mentor { + mentorView + .customNavigationTitle("No Upcoming Session") + } + Spacer() + } + .padding(.horizontal, 16) + .padding(.top, 28) + } + var bookSessionButton: some View { + return Button { + showCalendlyWebView = true + } label: { + ALUMText(text: "Book Session via Calendly", textColor: ALUMColor.white) + } + .sheet(isPresented: $showCalendlyWebView) { + CalendlyView() + } + .buttonStyle(FilledInButtonStyle()) + .padding(.bottom, 26) + } + + var mentorView: some View { + let pairedMenteeId = currentUser.pairedMenteeId! + + return Group { + HStack { + ALUMText(text: "Mentee", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + + NavigationLink(destination: MenteeProfileScreen(uID: pairedMenteeId)) { + HorizontalMenteeCard( + menteeId: pairedMenteeId, + isEmpty: true + ) + .padding(.bottom, 28) + } + } + } + + var menteeView: some View { + print("\(currentUser.uid) \(currentUser.isLoading) \(currentUser.pairedMenteeId) \(currentUser.pairedMentorId)") + let pairedMentorId = currentUser.pairedMentorId! + + return Group { + bookSessionButton + + HStack { + ALUMText(text: "Mentor", textColor: ALUMColor.gray4) + Spacer() + } + .padding(.bottom, 5) + + NavigationLink(destination: MentorProfileScreen(uID: pairedMentorId)) { + MentorCard(isEmpty: true, uID: pairedMentorId) + .padding(.bottom, 28) + } + } + + } +} + +struct HomeScreen_Previews: PreviewProvider { + static var previews: some View { + CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentee) + + CurrentUserModel.shared.status = "paired" + CurrentUserModel.shared.sessionId = "646a6e164082520f4fcf2f8f" + CurrentUserModel.shared.pairedMentorId = "6431b9a2bcf4420fe9825fe5" + return HomeScreen() + } +} diff --git a/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift new file mode 100644 index 00000000..9f546bd0 --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift @@ -0,0 +1,199 @@ +// +// MissedSessionScreen.swift +// ALUM +// +// Created by Jenny Mar on 4/5/23. +// + +import SwiftUI + +struct MissedSessionScreen: View { + enum OptionType { + case cancelled + case emergency + case forgot + case notShowed + case other + case notSay + } + + @ObservedObject var viewModel: QuestionViewModel + @Environment(\.dismiss) var dismiss + @State var validated: Bool = true + @State var selectedOption: OptionType? + @State var noOption: Bool = false + @State var otherEmpty: Bool = false + @State var otherText: String = "" + @State var otherUser: String = "Mentor" + @State var date: String = "date" + @State var time: String = "time" + + var body: some View { + GeometryReader { geometry in + ZStack { + Color("ALUM White 2").edgesIgnoringSafeArea(.all) + VStack { + ScrollView { + VStack { + ZStack { + RoundedRectangle(cornerRadius: 12) + .fill(Color("ALUM Light Blue")) + Text("You have successfully booked a session with \(otherUser) on \(date) at \(time).") + .font(.custom("Metropolis-Regular", size: 17)) + .lineSpacing(10) + .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 32) + .padding(.top, 8) + + HStack { + Text("What went wrong?") + .font(.custom("Metropolis-Regular", size: 17)) + .foregroundColor(Color("ALUM Dark Blue")) + .padding(.init(top: 0, leading: 16, bottom: 3, trailing: 16)) + Spacer() + } + if noOption == true { + HStack { + Text("Please select an option") + .font(.custom("Metropolis-Regular", size: 13)) + .foregroundColor(Color("FunctionalError")) + .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) + Spacer() + } + } else if otherEmpty == true { + HStack { + Text("Please fill in Other") + .font(.custom("Metropolis-Regular", size: 13)) + .foregroundColor(Color("FunctionalError")) + .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) + Spacer() + } + } + + MultipleChoice(content: "Mentor/ee and I decided to cancel", + checked: selectedOption == .cancelled, otherText: $otherText) + .onTapGesture {selectedOption = .cancelled + viewModel.missedOption = "Mentor/ee and I decided to cancel" + noOption = false; otherEmpty = false} + .padding(.bottom, 12) + MultipleChoice(content: "I had an emergency", + checked: selectedOption == .emergency, + otherText: $otherText) + .onTapGesture {selectedOption = .emergency + viewModel.missedOption = "I had an emergency" + noOption = false; otherEmpty = false} + .padding(.bottom, 12) + MultipleChoice(content: "I forgot about the session", + checked: selectedOption == .forgot, otherText: $otherText) + .onTapGesture {selectedOption = .forgot + viewModel.missedOption = "I forgot about the session" + noOption = false; otherEmpty = false} + .padding(.bottom, 12) + MultipleChoice(content: "Mentor/ee didn't show", + checked: selectedOption == .notShowed, otherText: $otherText) + .onTapGesture {selectedOption = .notShowed + viewModel.missedOption = "Mentor/ee didn't show" + noOption = false; otherEmpty = false} + .padding(.bottom, 12) + MultipleChoice(content: "Other:", + checked: selectedOption == .other, otherText: $otherText) + .onTapGesture {selectedOption = .other + viewModel.missedOption = otherText + noOption = false} + .padding(.bottom, 12) + MultipleChoice(content: "Prefer not to say", + checked: selectedOption == .notSay, otherText: $otherText) + .onTapGesture {selectedOption = .notSay + viewModel.missedOption = "Prefer not to say" + noOption = false; otherEmpty = false} + .padding(.bottom, 12) + + } + } + ZStack { + Rectangle() + .frame(maxWidth: .infinity) + .frame(height: 114) + .foregroundColor(Color.white) + .ignoresSafeArea(edges: .all) + if viewModel.missedOption != "" { + NavigationLink(destination: ConfirmationScreen( + text: ["Missed session form submitted!", + "Thank you for your feedback!", "Close"]), label: { + HStack { + Text("Submit") + .font(.custom("Metropolis-Regular", size: 17)) + } + }) + .buttonStyle(FilledInButtonStyle(disabled: false)) + .padding(.bottom, 32) + .frame(width: geometry.size.width * 0.92) + } else { + if selectedOption == .other { + + } else { + + } + Button("Submit") { + if selectedOption == .other { + otherEmpty = true + } else { + noOption = true + } + } + .font(.custom("Metropolis-Regular", size: 17)) + .buttonStyle(FilledInButtonStyle(disabled: true)) + .padding(.bottom, 32) + .frame(width: geometry.size.width * 0.92) + } + + } + .padding(.leading, 16) + .padding(.trailing, 16) + .frame(alignment: .bottom) + .frame(maxWidth: .infinity) + .background(Color.white.ignoresSafeArea(edges: .bottom)) + + } + .onAppear { + if viewModel.missedOption == "Mentor/ee and I decided to cancel" { + selectedOption = .cancelled + } else if viewModel.missedOption == "I had an emergency" { + selectedOption = .emergency + } else if viewModel.missedOption == "I forgot about the session" { + selectedOption = .forgot + } else if viewModel.missedOption == "Other" { + selectedOption = .other + } else if viewModel.missedOption == "Prefer not to say" { + selectedOption = .notSay + } + } + } +// (todo) re add +// .navigationBarItems(leading: NavigationLink( +// destination: PostSessionQuestionScreen(viewModel: viewModel), +// label: { +// HStack { +// Image(systemName: "chevron.left") +// Text("Cancel") +// .font(.custom("Metropolis-Regular", size: 13)) +// } +// } +// )) + .navigationBarTitle(Text("Missed Session").font(.custom("Metropolis-Regular", size: 17)), + displayMode: .inline) + .navigationBarBackButtonHidden() + } + } +} + +struct MissedSessionScreen_Previews: PreviewProvider { + static private var viewModel = QuestionViewModel() + + static var previews: some View { + MissedSessionScreen(viewModel: viewModel) + } +} diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift new file mode 100644 index 00000000..b7c5167b --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift @@ -0,0 +1,199 @@ +// +// PostSessionConfirmationScreen.swift +// ALUM +// +// Created by Jenny Mar on 4/11/23. +// + +import SwiftUI + +struct PostSessionConfirmationScreen: View { + @ObservedObject var viewModel: QuestionViewModel + + @State var notesID: String + @State var currNotes: String = "this" // "this" or "other" + + func setMyNotes() { + currNotes = "this" + } + + func setOtherNotes() { + currNotes = "other" + } + + var body: some View { + return VStack { + ScrollView { + content + } + footer + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 40) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) + } + .edgesIgnoringSafeArea(.bottom) + } + + var footer: some View { + HStack { + Button { + viewModel.prevQuestion() + } label: { + HStack { + Image(systemName: "arrow.left") + Text("Back") + } + } + .buttonStyle(OutlinedButtonStyle()) + + Spacer() + + Button { + Task { + do { + // (todo) remove hardcoding + try await viewModel.submitNotesPatch(noteID: notesID) + self.viewModel.submitSuccess = true + } catch { + print("Error") + } + } + } label: { + Text("Save") + } + .buttonStyle(FilledInButtonStyle()) + + NavigationLink(destination: ConfirmationScreen( + text: ["Post-session form saved!", + "You can continue on the notes later under \"Sessions\".", "Great"]), + isActive: $viewModel.submitSuccess) { + EmptyView() + } + } + } + + var content: some View { + VStack { + HStack { + Text("Summary") + .font(.custom("Metropolis-Regular", size: 34)) + .foregroundColor(Color("NeutralGray3")) + Spacer() + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 32) + .padding(.top, 8) + + HStack { + Button { + setMyNotes() + } label: { + if currNotes == "this" { + Text("MY NOTES") + .font(.custom("Metropolis-Regular", size: 16)) + .foregroundColor(Color("ALUM Dark Blue")) + .bold() + } else { + Text("MY NOTES") + .font(.custom("Metropolis-Regular", size: 16)) + .foregroundColor(Color("ALUM Dark Blue")) + } + + } + .padding(.trailing, 40) + Button { + setOtherNotes() + } label: { + if currNotes == "other" { + Text((viewModel.currentUser.role == UserRole.mentee) ? "MENTOR NOTES" : "MENTEE NOTES") + .font(.custom("Metropolis-Regular", size: 16)) + .foregroundColor(Color("ALUM Dark Blue")) + .bold() + } else { + Text((viewModel.currentUser.role == UserRole.mentee) ? "MENTOR NOTES" : "MENTEE NOTES") + .font(.custom("Metropolis-Regular", size: 16)) + .foregroundColor(Color("ALUM Dark Blue")) + } + } + Spacer() + } + .padding(.leading, 16) + ZStack { + Divider() + .background(Color("ALUM Light Blue")) + .frame(width: 358) + .padding(.bottom, 32) + if currNotes == "this" { + Divider() + .background(Color("ALUM Dark Blue").frame(height: 3)) + .frame(width: 84) + .padding(.bottom, 32) + .padding(.trailing, 275) + } else { + Divider() + .background(Color("ALUM Dark Blue").frame(height: 3)) + .frame(width: 129) + .padding(.bottom, 32) + .padding(.leading, 35) + } + } + + ForEach((currNotes == "this") ? viewModel.questionList : viewModel.questionListOther, + id: \.self) { currQuestion in + HStack { + Text(currQuestion.question) + .foregroundColor(Color("ALUM Dark Blue")) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + + Spacer() + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 8) + + if currQuestion.type == "text" { + HStack { + Text(currQuestion.answerParagraph) + .font(Font.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) + + Spacer() + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 32) + } else if currQuestion.type == "bullet" { + VStack { + ForEach(currQuestion.answerBullet, id: \.self) { item in + HStack { + Image(systemName: "circle.fill") + .foregroundColor(Color.black) + .font(.system(size: 5.0)) + Text(item) + .font(Font.custom("Metropolis-Regular", + size: 17, + relativeTo: .headline)) + .foregroundColor(.black) + .padding(.bottom, 2) + .lineSpacing(4.0) + Spacer() + } + .padding(.leading, 16) + .padding(.trailing, 16) + } + } + .padding(.bottom, 32) + } + } + } + } +} + +struct PostSessionConfirmationScreen_Previews: PreviewProvider { + static private var viewModel = QuestionViewModel() + + static var previews: some View { + PostSessionConfirmationScreen(viewModel: viewModel, notesID: "646a6e164082520f4fcf2f92") + } +} diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift new file mode 100644 index 00000000..8bb21acb --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift @@ -0,0 +1,79 @@ +// +// PostSessionFormRouter.swift +// ALUM +// +// Created by Aman Aggarwal on 5/27/23. +// + +import SwiftUI + +struct PostSessionFormRouter: View { + @StateObject private var viewModel = QuestionViewModel() + + var notesID: String + var otherNotesId: String + var otherName: String + var date: String + var time: String + + var body: some View { + loadingAbstraction + .customNavBarItems( + title: "\(date) Post-session Notes", + isPurple: false, + backButtonHidden: false + ) + } + + var loadingAbstraction: some View { + return Group { + if viewModel.isLoading { + LoadingView(text: "PostSessionFormRouter \(notesID)") + .onAppear { + Task { + do { + try await viewModel.fetchPostSessionNotes( + notesId: notesID, + otherNotesId: otherNotesId + ) + } catch { + print("ERROR PostSessionFormRouter \(error)") + } + } + } + } else { + loadedView + } + } + } + + var loadedView: some View { + print(viewModel.questionList) + return VStack { + DynamicProgressBarComponent(nodes: $viewModel.questionList.count + 1, + filledNodes: $viewModel.currentIndex, activeNode: $viewModel.currentIndex) + .padding() + .background(Color.white) + if viewModel.currentIndex < viewModel.questionList.count { + PostSessionQuestionScreen(viewModel: viewModel, otherUser: otherName, date: date, time: time) + } else { + PostSessionConfirmationScreen(viewModel: viewModel, notesID: notesID) + } + } + } +} + +struct PostSessionFormRouter_Previews: PreviewProvider { + static var previews: some View { + CustomNavView { + PostSessionFormRouter( + notesID: "646c8ea5004999f332c55f84", + otherNotesId: "646c8ea5004999f332c55f86", + otherName: "Mentor", + date: "5/23", + time: "9pm" + ) + } + } +} + diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift new file mode 100644 index 00000000..01b40700 --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift @@ -0,0 +1,165 @@ +// +// MenteePostSessionQuestionScreen.swift +// ALUM +// +// Created by Jenny Mar on 4/5/23. +// + +import SwiftUI + +struct PostSessionQuestionScreen: View { + + @ObservedObject var viewModel: QuestionViewModel + @Environment(\.dismiss) var dismiss + @State var otherUser: String + @State var date: String + @State var time: String + + var body: some View { + VStack { + ScrollView { + content + } + + footer + + } + .edgesIgnoringSafeArea(.bottom) + } + + + var content: some View { + var currentIndex = viewModel.currentIndex + if viewModel.currentIndex >= viewModel.questionList.count { + currentIndex = viewModel.questionList.count - 1 + } + let currentQuestion = viewModel.questionList[currentIndex] + + return VStack { + if currentIndex == 0 { + ZStack { + RoundedRectangle(cornerRadius: 12) + .fill(Color("ALUM Light Blue")) + VStack { + Text("Let's reflect on your session with \(otherUser) on \(date) at \(time).") + .font(.custom("Metropolis-Regular", size: 17)) + .lineSpacing(10) + .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) + Spacer() + NavigationLink(destination: MissedSessionScreen(viewModel: viewModel), label: { + HStack { + Text("Session didn't happen?") + .foregroundColor(Color("ALUM Dark Blue")) + .underline() + .padding(.init(top: 0, leading: 16, bottom: 8, trailing: 16)) + Spacer() + } + }) + } + } + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.bottom, 32) + .padding(.top, 8) + } + + if currentQuestion.type == "text" { + ParagraphInput(question: currentQuestion.question, + text: $viewModel.questionList[currentIndex].answerParagraph) + .padding(.leading, 16) + .padding(.trailing, 16) + .padding(.top, 8) + } else if currentQuestion.type == "bullet" { + Text(viewModel.questionList[viewModel.currentIndex].question) + .foregroundColor(Color("ALUM Dark Blue")) + .font(Font.custom("Metropolis-Regular", size: 17)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.bottom, 16) + .padding(.top, 8) + BulletsView(bullets: $viewModel.questionList[currentIndex].answerBullet, + question: currentQuestion.question) + } else if currentQuestion.type == "checkbox-bullet" { + Text(currentQuestion.question) + .foregroundColor(Color("ALUM Dark Blue")) + .font(Font.custom("Metropolis-Regular", size: 17)) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 16) + .padding(.bottom, 16) + .padding(.top, 8) + CheckboxBulletsView( + checkboxBullets: + $viewModel.questionList[currentIndex].answerCheckboxBullet, + question: currentQuestion.question) + } + } + } +} + +// Footer Code goes here +extension PostSessionQuestionScreen { + @ViewBuilder + var footer: some View { + Group { + if viewModel.currentIndex == 0 { + footerForFirstQuestion + } else { + footerWithBackAndContinue + } + } + .padding(.horizontal, 16) + .padding(.top, 32) + .padding(.bottom, 40) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) + } + + var footerForFirstQuestion: some View { + Button { + viewModel.nextQuestion() + } label: { + HStack { + ALUMText(text: "Continue", textColor: ALUMColor.white) + Image(systemName: "arrow.right") + .font(.system(size: 17)) + .foregroundColor(Color.white) + } + } + .buttonStyle(FilledInButtonStyle()) + } + + var footerWithBackAndContinue: some View { + HStack { + Button { + viewModel.prevQuestion() + } label: { + HStack { + Image(systemName: "arrow.left") + Text("Back") + } + } + .buttonStyle(OutlinedButtonStyle()) + + Spacer() + + Button { + viewModel.nextQuestion() + } label: { + HStack { + ALUMText(text: "Continue", textColor: ALUMColor.white) + Image(systemName: "arrow.right") + .font(.system(size: 17)) + .foregroundColor(Color.white) + } + } + .buttonStyle(FilledInButtonStyle()) + } + } +} +// +//struct PostSessionQuestionScreen_Previews: PreviewProvider { +// static private var viewModel = QuestionViewModel() +// +// static var previews: some View { +// PostSessionQuestionScreen(viewModel: viewModel) +// } +//} diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift new file mode 100644 index 00000000..e2524faa --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift @@ -0,0 +1,76 @@ +// +// PostSessionView.swift +// ALUM +// +// Created by Jenny Mar on 4/5/23. +// + +import SwiftUI + +struct PostSessionScreenHeaderModifier: ViewModifier { + func body(content: Content) -> some View { + VStack { + VStack { + NavigationHeaderComponent( + backText: "XXX", + backDestination: Text("TODO Blank"), + title: "Post-session Notes", + purple: false + ) + } + content + .background(Color("ALUM White 2")) + } + } +} + +extension View { + func applyPostSessionScreenHeaderModifier() -> some View { + self.modifier(PostSessionScreenHeaderModifier()) + } +} + +struct PostSessionView: View { + @StateObject private var viewModel = QuestionViewModel() + + @State var notesID: String = "" + @State var otherNotesID: String = "" + + @State var otherName: String = "" + @State var date: String = "" + @State var time: String = "" + + var body: some View { + Group { + if !viewModel.isLoading { + PostSessionQuestionScreen( + viewModel: viewModel, + otherUser: otherName, + date: date, + time: time + ) + .navigationBarTitle("", displayMode: .inline) + .navigationBarHidden(true) + } else { + Text("Loading...") + .navigationBarTitle("") + .navigationBarHidden(true) + .onAppear { + Task { + do { + try await viewModel.fetchPostSessionNotes(notesId: notesID, otherNotesId: otherNotesID) + } catch { + print("Error \(error)") + } + } + } + } + } + } +} + +struct PostSessionView_Previews: PreviewProvider { + static var previews: some View { + PostSessionView() + } +} From 06729a7b81c1615dda44aa2eb4f94999aa1d32b2 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 22:22:49 -0700 Subject: [PATCH 24/33] integrate missed session form changes with new session details page --- ALUM/ALUM/Models/SessionModel.swift | 1 + .../PostSessionForm/MissedSessionScreen.swift | 20 +++--- .../PostSessionFormRouter.swift | 2 +- .../PostSessionQuestionScreen.swift | 21 ++++-- ALUM/ALUM/Views/SessionDetailsScreen.swift | 69 ++++++++++++------- backend/src/services/note.ts | 2 - 6 files changed, 70 insertions(+), 45 deletions(-) diff --git a/ALUM/ALUM/Models/SessionModel.swift b/ALUM/ALUM/Models/SessionModel.swift index 7ac3862a..fb3b261a 100644 --- a/ALUM/ALUM/Models/SessionModel.swift +++ b/ALUM/ALUM/Models/SessionModel.swift @@ -39,4 +39,5 @@ struct SessionModel: Decodable { var postSessionMentorCompleted: Bool var hasPassed: Bool var location: String + var missedSessionReason: String? } diff --git a/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift index 43d27251..c312d019 100644 --- a/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift @@ -18,17 +18,19 @@ struct MissedSessionScreen: View { } @ObservedObject var viewModel: QuestionViewModel + var notesID: String + var date: String + var time: String + var otherUser: String + @Environment(\.dismiss) var dismiss @State var validated: Bool = true @State var selectedOption: OptionType? @State var noOption: Bool = false @State var otherEmpty: Bool = false @State var otherText: String = "" - @State var otherUser: String = "Mentor" - @State var date: String = "date" - @State var time: String = "time" - @State var notesID: String = "" + var body: some View { VStack { ScrollView { @@ -53,7 +55,6 @@ struct MissedSessionScreen: View { } } .edgesIgnoringSafeArea(.bottom) - .applyMissedSessionScreenHeaderModifier() } var footer: some View { @@ -83,7 +84,7 @@ struct MissedSessionScreen: View { Text("Submit") } .buttonStyle(FilledInButtonStyle()) - NavigationLink(destination: SessionConfirmationScreen( + NavigationLink(destination: ConfirmationScreen( text: ["Missed session form submitted!", "Thank you for your feedback!", "Close"]), isActive: $viewModel.submitSuccess) { @@ -98,8 +99,7 @@ struct MissedSessionScreen: View { ZStack { RoundedRectangle(cornerRadius: 12) .fill(Color("ALUM Light Blue")) - Text("You have successfully booked a session with \(otherUser) on \(date) at \(time).") - .font(.custom("Metropolis-Regular", size: 17)) + ALUMText(text: "It seems that your session with \(otherUser) on \(date) at \(time) didn’t happen.") .lineSpacing(10) .padding(.init(top: 16, leading: 16, bottom: 8, trailing: 16)) } @@ -180,6 +180,6 @@ struct MissedSessionScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - MissedSessionScreen(viewModel: viewModel) + MissedSessionScreen(viewModel: viewModel, notesID: "646a6e164082520f4fcf2f92", date: "9/23", time: "10:00 AM", otherUser: "Mentor") } -} \ No newline at end of file +} diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift index 8bb21acb..e8188b57 100644 --- a/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift @@ -55,7 +55,7 @@ struct PostSessionFormRouter: View { .padding() .background(Color.white) if viewModel.currentIndex < viewModel.questionList.count { - PostSessionQuestionScreen(viewModel: viewModel, otherUser: otherName, date: date, time: time) + PostSessionQuestionScreen(viewModel: viewModel, otherUser: otherName, date: date, time: time, noteId: notesID) } else { PostSessionConfirmationScreen(viewModel: viewModel, notesID: notesID) } diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift index a56d66d5..bf98f1a9 100644 --- a/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift @@ -10,10 +10,11 @@ import SwiftUI struct PostSessionQuestionScreen: View { @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var otherUser: String - @State var date: String - @State var time: String + + var otherUser: String + var date: String + var time: String + var noteId: String var body: some View { VStack { @@ -46,8 +47,16 @@ struct PostSessionQuestionScreen: View { .lineSpacing(10) .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) Spacer() - NavigationLink(destination: MissedSessionScreen( - viewModel: viewModel, notesID: notesID), label: { + NavigationLink( + destination: + MissedSessionScreen( + viewModel: viewModel, + notesID: noteId, + date: date, + time: time, + otherUser: otherUser + ), + label: { HStack { Text("Session didn't happen?") .foregroundColor(Color("ALUM Dark Blue")) diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index 6addaf57..a4cfa2a6 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -227,19 +227,48 @@ struct SessionDetailsScreen: View { .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 5) } - - CustomNavLink( - destination: ViewPreSessionNotesPage( - allowEditing: false, - notesID: session.preSession, - otherName: otherName - ), - label: { - ALUMText(text: "View Pre-Session Notes") + } + } + + var viewPreSessionNotesForAny: some View { + let session = viewModel.session! + var otherName: String + switch currentUser.role { + case .mentee: + otherName = session.mentorName + case .mentor: + otherName = session.menteeName + case .none: + otherName = "" + } + return CustomNavLink( + destination: ViewPreSessionNotesPage( + allowEditing: false, + notesID: session.preSession, + otherName: otherName + ), + label: { + ALUMText(text: "View Pre-Session Notes") + } + ) + .buttonStyle(OutlinedButtonStyle()) + .padding(.bottom, 5) + } + + var afterEventSectionForAny: some View { + let session = viewModel.session! + + return VStack { + if session.missedSessionReason == nil { + postSessionNotesSectionForAny + } else { + HStack { + ALUMText(text: "Session was not completed due to: \(session.missedSessionReason!)", textColor: ALUMColor.red) + Spacer() } - ) - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 5) + .padding(.bottom, 10) + } + viewPreSessionNotesForAny } } } @@ -270,7 +299,7 @@ extension SessionDetailsScreen { } dateTimeDisplaySection if session.hasPassed { - afterEventSectionMentor + afterEventSectionForAny } else { beforeEventSectionMentor } @@ -284,12 +313,6 @@ extension SessionDetailsScreen { } } - var afterEventSectionMentor: some View { - return VStack { - postSessionNotesSectionForAny - } - } - var preSessionNotesSectionForMentor: some View { // Mentor can view mentee's pre-session notes let session = viewModel.session! @@ -339,7 +362,7 @@ extension SessionDetailsScreen { dateTimeDisplaySection if session.hasPassed { - afterEventSectionMentee + afterEventSectionForAny } else { beforeEventSectionMentee } @@ -347,12 +370,6 @@ extension SessionDetailsScreen { } - var afterEventSectionMentee: some View { - return VStack { - postSessionNotesSectionForAny - } - } - var beforeEventSectionMentee: some View { return Group { locationSectionForAny diff --git a/backend/src/services/note.ts b/backend/src/services/note.ts index 1736fe57..7d7eed53 100644 --- a/backend/src/services/note.ts +++ b/backend/src/services/note.ts @@ -119,9 +119,7 @@ async function updateNotes(updatedNotes: UpdateNoteDetailsType[], documentId: st const checkMissedNote = updatedNotes.find( (note) => note.questionId === "missedSessionQuestionId" ); - console.log("here"); if (checkMissedNote) { - console.log("checkMissedNote is true"); missedNote = true; missedReason = checkMissedNote.answer; } From 769a54bc61e5d9cf7742e45cbd1da940ab5e9a71 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 22:44:38 -0700 Subject: [PATCH 25/33] fix swiftlint errors except SessionDetailsScreen.swift file length --- .../CustomNavBarContainerView.swift | 23 ++-- .../CustomNavBarPreferenceKeys.swift | 20 +-- .../CustomNavBar/CustomNavLink.swift | 4 +- .../CustomNavBar/CustomNavView.swift | 2 +- .../Components/HorizontalMenteeCard.swift | 10 +- ALUM/ALUM/Constants/ALUMText.swift | 9 +- ALUM/ALUM/Constants/DevelopmentModels.swift | 124 +++++++++--------- ALUM/ALUM/CustomErrors.swift | 4 +- ALUM/ALUM/Models/CurrentUserModel.swift | 14 +- ALUM/ALUM/Services/APIConfig.swift | 11 +- ALUM/ALUM/Services/UserService.swift | 8 +- ALUM/ALUM/ViewModels/QuestionViewModel.swift | 8 +- .../ViewModels/SessionDetailViewModel.swift | 4 +- ALUM/ALUM/Views/HomeScreen.swift | 26 ++-- ALUM/ALUM/Views/LoadingScreen.swift | 2 +- ALUM/ALUM/Views/MenteeProfileScreen.swift | 22 +++- ALUM/ALUM/Views/MentorProfileScreen.swift | 26 ++-- .../PostSessionForm/MissedSessionScreen.swift | 11 +- .../PostSessionConfirmationScreen.swift | 2 +- .../PostSessionFormRouter.swift | 27 ++-- .../PostSessionQuestionScreen.swift | 15 +-- .../ViewPostSessionNotesPage.swift | 29 ++-- .../PreSessionConfirmationScreen.swift | 3 +- .../PreSessionForm/PreSessionFormRouter.swift | 10 +- .../PreSessionQuestionScreen.swift | 11 +- .../ViewPreSessionNotesPage.swift | 25 ++-- ALUM/ALUM/Views/Routers/HomeTabRouter.swift | 3 +- ALUM/ALUM/Views/Routers/LoggedInRouter.swift | 23 ++-- ALUM/ALUM/Views/Routers/RootRouter.swift | 12 +- ALUM/ALUM/Views/SessionDetailsScreen.swift | 117 +++++++++-------- 30 files changed, 332 insertions(+), 273 deletions(-) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift index 06b4e926..4d3721c2 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -16,20 +16,19 @@ struct CustomNavBarDefaultValues { struct CustomNavBarContainerView: View { let content: Content - + // By default, we show the back button unless some view explicitly sets // this to false - @State private var showBackButton: Bool = CustomNavBarDefaultValues.showBackButton - + @State private var showBackButton: Bool = CustomNavBarDefaultValues.showBackButton + @State private var title: String = CustomNavBarDefaultValues.title @State private var isPurple: Bool = CustomNavBarDefaultValues.barIsPurple @State private var isHidden: Bool = CustomNavBarDefaultValues.barIsHidden - - + init(@ViewBuilder content: () -> Content) { self.content = content() } - + var body: some View { return VStack(spacing: 0) { if !isHidden { @@ -38,20 +37,16 @@ struct CustomNavBarContainerView: View { content .frame(maxWidth: .infinity, maxHeight: .infinity) } - .onPreferenceChange(CustomNavBarTitlePreferenceKey.self, perform: { - value in + .onPreferenceChange(CustomNavBarTitlePreferenceKey.self, perform: { value in self.title = value }) - .onPreferenceChange(CustomNavBarIsPurplePreferenceKey.self, perform: { - value in + .onPreferenceChange(CustomNavBarIsPurplePreferenceKey.self, perform: { value in self.isPurple = value }) - .onPreferenceChange(CustomNavBarBackButtonHiddenPreferenceKey.self, perform: { - value in + .onPreferenceChange(CustomNavBarBackHiddenPreferenceKey.self, perform: { value in self.showBackButton = !value }) - .onPreferenceChange(CustomNavBarIsHiddenPreferenceKey.self, perform: { - value in + .onPreferenceChange(CustomNavBarIsHiddenPreferenceKey.self, perform: { value in self.isHidden = value }) } diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift index 67b06375..d5510e0a 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -10,7 +10,7 @@ import SwiftUI struct CustomNavBarTitlePreferenceKey: PreferenceKey { static var defaultValue: String = CustomNavBarDefaultValues.title - + static func reduce(value: inout String, nextValue: () -> String) { value = nextValue() } @@ -18,7 +18,7 @@ struct CustomNavBarTitlePreferenceKey: PreferenceKey { struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { static var defaultValue: Bool = CustomNavBarDefaultValues.barIsHidden - + static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() } @@ -26,15 +26,15 @@ struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { static var defaultValue: Bool = CustomNavBarDefaultValues.barIsPurple - + static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() } } -struct CustomNavBarBackButtonHiddenPreferenceKey: PreferenceKey { +struct CustomNavBarBackHiddenPreferenceKey: PreferenceKey { static var defaultValue: Bool = !CustomNavBarDefaultValues.showBackButton - + static func reduce(value: inout Bool, nextValue: () -> Bool) { value = nextValue() } @@ -44,19 +44,19 @@ extension View { func customNavigationIsHidden(_ isHidden: Bool) -> some View { preference(key: CustomNavBarIsHiddenPreferenceKey.self, value: isHidden) } - + func customNavigationTitle(_ title: String) -> some View { preference(key: CustomNavBarTitlePreferenceKey.self, value: title) } - + func customNavigationIsPurple(_ isPurple: Bool) -> some View { preference(key: CustomNavBarIsPurplePreferenceKey.self, value: isPurple) } - + func customNavigationBarBackButtonHidden(_ hidden: Bool) -> some View { - preference(key: CustomNavBarBackButtonHiddenPreferenceKey.self, value: hidden) + preference(key: CustomNavBarBackHiddenPreferenceKey.self, value: hidden) } - + func customNavBarItems(title: String, isPurple: Bool, backButtonHidden: Bool) -> some View { self .customNavigationTitle(title) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift index 4b5eebba..0d895c2b 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -16,12 +16,12 @@ struct CustomNavLink: View { } var body: some View { NavigationLink( - destination: + destination: CustomNavBarContainerView(content: { destination }) .navigationBarHidden(true) - + , label: { label }) diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift index 831d14fa..875762c9 100644 --- a/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavView.swift @@ -7,7 +7,7 @@ import SwiftUI -struct CustomNavView: View { +struct CustomNavView: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() diff --git a/ALUM/ALUM/Components/HorizontalMenteeCard.swift b/ALUM/ALUM/Components/HorizontalMenteeCard.swift index 5f8c9e1a..7ada0c6b 100644 --- a/ALUM/ALUM/Components/HorizontalMenteeCard.swift +++ b/ALUM/ALUM/Components/HorizontalMenteeCard.swift @@ -9,15 +9,15 @@ import SwiftUI struct HorizontalMenteeCard: View { var menteeId: String - + var school: String = "NHS" @State var isEmpty = true @StateObject private var viewModel = MenteeProfileViewmodel() - + var body: some View { loadingAbstraction } - + var loadingAbstraction: some View { Group { if viewModel.isLoading() || viewModel.mentee == nil { @@ -35,10 +35,10 @@ struct HorizontalMenteeCard: View { } }) } - + var loadedView: some View { let mentee = viewModel.mentee! - + return ZStack { RoundedRectangle(cornerRadius: 12.0) .frame(width: 358, height: 118) diff --git a/ALUM/ALUM/Constants/ALUMText.swift b/ALUM/ALUM/Constants/ALUMText.swift index b208e1e8..d99f5f40 100644 --- a/ALUM/ALUM/Constants/ALUMText.swift +++ b/ALUM/ALUM/Constants/ALUMText.swift @@ -24,8 +24,13 @@ struct ALUMText: View { let textColor: ALUMColor let isUnderlined: Bool - init(text: String, fontName: ALUMFontName = .bodyFontName, - fontSize: ALUMFontSize = .bodyFontSize, textColor: ALUMColor = ALUMColor.primaryPurple, isUnderlined: Bool = false) { + init( + text: String, + fontName: ALUMFontName = .bodyFontName, + fontSize: ALUMFontSize = .bodyFontSize, + textColor: ALUMColor = ALUMColor.primaryPurple, + isUnderlined: Bool = false + ) { self.text = text self.fontName = fontName self.fontSize = fontSize diff --git a/ALUM/ALUM/Constants/DevelopmentModels.swift b/ALUM/ALUM/Constants/DevelopmentModels.swift index 4fbcef7c..69c9723a 100644 --- a/ALUM/ALUM/Constants/DevelopmentModels.swift +++ b/ALUM/ALUM/Constants/DevelopmentModels.swift @@ -9,96 +9,96 @@ import Foundation class DevelopmentModels { static var sessionModel: SessionModel = SessionModel( - preSession: "6464276b6f05d9703f069761", - postSessionMentee: Optional("6464276b6f05d9703f069763"), - postSessionMentor: Optional("6464276b6f05d9703f069765"), - menteeId: "6431b99ebcf4420fe9825fe3", - mentorId: "6431b9a2bcf4420fe9825fe5", + preSession: "6464276b6f05d9703f069761", + postSessionMentee: Optional("6464276b6f05d9703f069763"), + postSessionMentor: Optional("6464276b6f05d9703f069765"), + menteeId: "6431b99ebcf4420fe9825fe3", + mentorId: "6431b9a2bcf4420fe9825fe5", menteeName: "Mentee Name", mentorName: "Mentor Name", fullDateString: "Thursday, May 18, 2023", dateShortHandString: "5/18", startTimeString: "11:00 AM", endTimeString: "11:30 AM", - preSessionCompleted: true, - postSessionMenteeCompleted: false, - postSessionMentorCompleted: false, - hasPassed: false, + preSessionCompleted: true, + postSessionMenteeCompleted: false, + postSessionMentorCompleted: false, + hasPassed: false, location: "https://alum.zoom.us/my/timby" ) - + static var menteeModel: MenteeInfo = MenteeInfo( - id: "6431b99ebcf4420fe9825fe3", - name: "Mentee", - imageId: "640b86513c48ef1b07904241", - about: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", - grade: 9, - topicsOfInterest: ["computer science"], - careerInterests: ["software development"], - mentorshipGoal: nil, - mentorId: nil, - status: nil, + id: "6431b99ebcf4420fe9825fe3", + name: "Mentee", + imageId: "640b86513c48ef1b07904241", + about: "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + grade: 9, + topicsOfInterest: ["computer science"], + careerInterests: ["software development"], + mentorshipGoal: nil, + mentorId: nil, + status: nil, whyPaired: Optional("Modified Why Paired") ) - + static var postSessionFormModel: [Question] = [ Question( - question: "What topics did you discuss?", - type: "bullet", - id: "4cb4504d3308126edee7ef72b7e04a04db8a1f5d45f8137fcb2717a33515de8b", - answerBullet: [], - answerCheckboxBullet: [], + question: "What topics did you discuss?", + type: "bullet", + id: "4cb4504d3308126edee7ef72b7e04a04db8a1f5d45f8137fcb2717a33515de8b", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" - ), + ), Question( - question: "Key takeaways from the session:", - type: "bullet", - id: "53f0ce23b3cb957455ded4c35a1fc7047b2365174b0cf05d2e945a31fde0d881", - answerBullet: ["Sasdlkfna;slkdfasdf;a"], - answerCheckboxBullet: [], + question: "Key takeaways from the session:", + type: "bullet", + id: "53f0ce23b3cb957455ded4c35a1fc7047b2365174b0cf05d2e945a31fde0d881", + answerBullet: ["Sasdlkfna;slkdfasdf;a"], + answerCheckboxBullet: [], answerParagraph: "" - ), + ), Question( - question: "Next step(s):", - type: "bullet", - id: "6e4e9e6195735e254e25a9663977ccb51255717f0880726899788375b21e2c30", - answerBullet: [], - answerCheckboxBullet: [], + question: "Next step(s):", + type: "bullet", + id: "6e4e9e6195735e254e25a9663977ccb51255717f0880726899788375b21e2c30", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" - ), + ), Question( - question: "Other notes:", - type: "text", - id: "fc58a4a3bfb853c240d3b9854695a7057d022a3b4dc1ec651cd0b9e2ef88ae8e", - answerBullet: [], - answerCheckboxBullet: [], + question: "Other notes:", + type: "text", + id: "fc58a4a3bfb853c240d3b9854695a7057d022a3b4dc1ec651cd0b9e2ef88ae8e", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" ) ] - + static var preSessionFormModel: [Question] = [ Question( - question: "What topic(s) would you like to discuss?", - type: "bullet", - id: "3486cca0ff5e75620cb5cded01041c45751d0ac93a068de3f4cd925b87cdff5f", - answerBullet: [], - answerCheckboxBullet: [], + question: "What topic(s) would you like to discuss?", + type: "bullet", + id: "3486cca0ff5e75620cb5cded01041c45751d0ac93a068de3f4cd925b87cdff5f", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" - ), + ), Question( - question: "Do you have any specifc question(s)?", - type: "bullet", - id: "f929836eee49ca458ae32ad89164bdb31e5749a5606c15b147a61d69c7cac8fd", - answerBullet: [], - answerCheckboxBullet: [], + question: "Do you have any specifc question(s)?", + type: "bullet", + id: "f929836eee49ca458ae32ad89164bdb31e5749a5606c15b147a61d69c7cac8fd", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" - ), + ), Question( - question: "Anything else that you want your mentor to know?", - type: "text", - id: "9bb08261232461b5dfbdea48578c6054adf7fd8639d815b4143080d0c16ec590", - answerBullet: [], - answerCheckboxBullet: [], + question: "Anything else that you want your mentor to know?", + type: "text", + id: "9bb08261232461b5dfbdea48578c6054adf7fd8639d815b4143080d0c16ec590", + answerBullet: [], + answerCheckboxBullet: [], answerParagraph: "" ) ] diff --git a/ALUM/ALUM/CustomErrors.swift b/ALUM/ALUM/CustomErrors.swift index 60ef88e3..635f9b35 100644 --- a/ALUM/ALUM/CustomErrors.swift +++ b/ALUM/ALUM/CustomErrors.swift @@ -36,11 +36,11 @@ func handleDecodingErrors(_ decodingClosure: () throws -> T) throws -> T { errorMessage = "Key '\(key)' not found, context: \(context.debugDescription)" } catch let DecodingError.valueNotFound(value, context) { errorMessage = "Value '\(value)' not found, context: \(context.debugDescription)" - } catch let DecodingError.typeMismatch(type, context) { + } catch let DecodingError.typeMismatch(type, context) { errorMessage = "Type '\(type)' mismatch:, context: \(context.debugDescription)" } catch { errorMessage = "Unknown: \(error)" } - + throw AppError.internalError(.invalidResponse, message: "Decode error - \(errorMessage)") } diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 59e77bd3..316cc6c5 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -89,20 +89,20 @@ class CurrentUserModel: ObservableObject { self.isLoading = false } } - + func fetchUserInfoFromServer(userId: String, role: UserRole) async throws { let userData = try await UserService.shared.getSelf() let userStatus = userData.status - + DispatchQueue.main.async { self.status = userStatus } - + if userStatus != "paired" { print("early return") return } - + if self.role == .mentee { guard let userPairedMentorId = userData.pairedMentorId else { throw AppError.internalError(.invalidResponse, message: "Expected mentee to have a paired mentor Id") @@ -119,13 +119,13 @@ class CurrentUserModel: ObservableObject { DispatchQueue.main.async { self.pairedMenteeId = userPairedMenteeId } - } - + } + DispatchQueue.main.async { self.sessionId = userData.sessionId } } - + func getStatus(userID: String, roleEnum: UserRole) async throws -> String { let userStatus: String switch roleEnum { diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 3b799b16..41fed8f9 100644 --- a/ALUM/ALUM/Services/APIConfig.swift +++ b/ALUM/ALUM/Services/APIConfig.swift @@ -72,7 +72,16 @@ enum APIRoute { var requireAuth: Bool { switch self { - case .getSelf, .getMentor, .getMentee, .getNote, .patchNote, .getSession, .getSessions, .postSession, .getCalendly: + case + .getSelf, + .getMentor, + .getMentee, + .getNote, + .patchNote, + .getSession, + .getSessions, + .postSession, + .getCalendly: return true case .postMentee, .postMentor: return false diff --git a/ALUM/ALUM/Services/UserService.swift b/ALUM/ALUM/Services/UserService.swift index bb02fd0b..414076b0 100644 --- a/ALUM/ALUM/Services/UserService.swift +++ b/ALUM/ALUM/Services/UserService.swift @@ -63,11 +63,11 @@ class UserService { let userData = try handleDecodingErrors({ try JSONDecoder().decode(SelfGetData.self, from: responseData) }) - + print("SUCCESS - \(route.label) - \(userData.pairedMenteeId) - \(userData.pairedMentorId)") return userData } - + func createMentee(data: MenteePostData) async throws { let route = APIRoute.postMentee var request = try await route.createURLRequest() @@ -105,11 +105,11 @@ class UserService { let route = APIRoute.getMentee(userId: userID) let request = try await route.createURLRequest() let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) - + let menteeData = try handleDecodingErrors({ try JSONDecoder().decode(MenteeGetData.self, from: responseData) }) - + print("SUCCESS - \(route.label)") return menteeData } diff --git a/ALUM/ALUM/ViewModels/QuestionViewModel.swift b/ALUM/ALUM/ViewModels/QuestionViewModel.swift index 37e41569..a0aa9c71 100644 --- a/ALUM/ALUM/ViewModels/QuestionViewModel.swift +++ b/ALUM/ALUM/ViewModels/QuestionViewModel.swift @@ -25,7 +25,7 @@ final class QuestionViewModel: ObservableObject { type: "text", questionId: "missedSessionQuestionId")) try await NotesService.shared.patchNotes(noteId: noteID, data: notesData) } - + func submitNotesPatch(noteID: String) async throws { var notesData: [QuestionPatchData] = [] @@ -51,7 +51,7 @@ final class QuestionViewModel: ObservableObject { } return newQuestions } - + func fetchPreSessionNotes(noteId: String) async throws { DispatchQueue.main.async { self.isLoading = true @@ -69,14 +69,14 @@ final class QuestionViewModel: ObservableObject { } let primaryQuestions = try await self.fetchNotes(noteId: notesId) let otherQuestions = try await self.fetchNotes(noteId: otherNotesId) - + DispatchQueue.main.async { self.isLoading = false self.questionList = primaryQuestions self.questionListOther = otherQuestions } } - + func nextQuestion() { self.currentIndex += 1 if self.currentIndex == self.questionList.count - 1 { diff --git a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift index f04e142a..12a9509a 100644 --- a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift +++ b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift @@ -12,7 +12,7 @@ final class SessionDetailViewModel: ObservableObject { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared @Published var session: SessionModel? = DevelopmentModels.sessionModel - + @Published var formIsComplete: Bool = false @Published var sessionCompleted: Bool = false @Published var isLoading: Bool = true @@ -22,7 +22,7 @@ final class SessionDetailViewModel: ObservableObject { self.isLoading = true } do { - let sessionData = try await SessionService.shared.getSessionWithId(sessionId: sessionId); + let sessionData = try await SessionService.shared.getSessionWithId(sessionId: sessionId) DispatchQueue.main.async { self.session = sessionData.session self.isLoading = false diff --git a/ALUM/ALUM/Views/HomeScreen.swift b/ALUM/ALUM/Views/HomeScreen.swift index 832d65f9..8ada98f4 100644 --- a/ALUM/ALUM/Views/HomeScreen.swift +++ b/ALUM/ALUM/Views/HomeScreen.swift @@ -15,10 +15,9 @@ struct HomeScreen: View { loadedView .background(ALUMColor.beige.color) .customNavigationIsPurple(false) - .customNavigationBarBackButtonHidden(true) + .customNavigationBarBackButtonHidden(true) } - - + var loadedView: some View { return VStack { if currentUser.role == .mentee { @@ -45,17 +44,17 @@ struct HomeScreen: View { .buttonStyle(FilledInButtonStyle()) .padding(.bottom, 26) } - + var mentorView: some View { let pairedMenteeId = currentUser.pairedMenteeId! - + return Group { HStack { ALUMText(text: "Mentee", textColor: ALUMColor.gray4) Spacer() } .padding(.bottom, 5) - + NavigationLink(destination: MenteeProfileScreen(uID: pairedMenteeId)) { HorizontalMenteeCard( menteeId: pairedMenteeId, @@ -65,20 +64,20 @@ struct HomeScreen: View { } } } - + var menteeView: some View { print("\(currentUser.uid) \(currentUser.isLoading) \(currentUser.pairedMenteeId) \(currentUser.pairedMentorId)") let pairedMentorId = currentUser.pairedMentorId! - + return Group { bookSessionButton - + HStack { ALUMText(text: "Mentor", textColor: ALUMColor.gray4) Spacer() } .padding(.bottom, 5) - + NavigationLink(destination: MentorProfileScreen(uID: pairedMentorId)) { MentorCard(isEmpty: true, uID: pairedMentorId) .padding(.bottom, 28) @@ -90,7 +89,12 @@ struct HomeScreen: View { struct HomeScreen_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentee) + CurrentUserModel.shared.setCurrentUser( + isLoading: false, + isLoggedIn: true, + uid: "6431b99ebcf4420fe9825fe3", + role: .mentee + ) CurrentUserModel.shared.status = "paired" CurrentUserModel.shared.sessionId = "646a6e164082520f4fcf2f8f" diff --git a/ALUM/ALUM/Views/LoadingScreen.swift b/ALUM/ALUM/Views/LoadingScreen.swift index e2cd4ebd..dedaa113 100644 --- a/ALUM/ALUM/Views/LoadingScreen.swift +++ b/ALUM/ALUM/Views/LoadingScreen.swift @@ -14,7 +14,7 @@ struct LoadingView: View { var body: some View { VStack { Spacer() - Text(text) +// Text(text) ProgressView() Spacer() } diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index cb2ecff2..ac70020f 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -54,6 +54,9 @@ struct MenteeProfileScreen: View { description if viewModel.selfView! { mentor + Button("Title", action: { + FirebaseAuthenticationService.shared.logout() + }) } } .frame(minHeight: grr.size.height - 50) @@ -64,7 +67,7 @@ struct MenteeProfileScreen: View { ZStack { // Removing this Z-stack causes a white rectangle to appear between the top of screen // and start of this screen due to GeometryReader - + if viewModel.selfView! { // params like settings and edit profile currently placeholders for later navigation if scrollAtTop { @@ -74,8 +77,7 @@ struct MenteeProfileScreen: View { ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) .background(.white) } - } - else { + } else { if scrollAtTop { Rectangle() .frame(height: 10) @@ -142,7 +144,7 @@ extension MenteeProfileScreen { .padding(.bottom, 8) } } - + private var mentor: some View { Group { Text("My Mentor") @@ -173,12 +175,20 @@ extension MenteeProfileScreen { struct MenteeProfileView_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b99ebcf4420fe9825fe3", role: .mentor) + CurrentUserModel.shared.setCurrentUser( + isLoading: false, + isLoggedIn: true, + uid: "6431b99ebcf4420fe9825fe3", + role: .mentor + ) return CustomNavView { MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") .onAppear { Task { - try await FirebaseAuthenticationService.shared.login(email: "mentor@gmail.com", password: "123456") + try await FirebaseAuthenticationService.shared.login( + email: "mentor@gmail.com", + password: "123456" + ) } } } diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 0408ac27..c0ed4c28 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -39,7 +39,7 @@ struct MentorProfileScreen: View { var content: some View { let mentor = viewModel.mentor! - + return GeometryReader { grr in VStack(spacing: 0) { ScrollView { @@ -71,6 +71,9 @@ struct MentorProfileScreen: View { about if viewModel.selfView! { mentees + Button("Title", action: { + FirebaseAuthenticationService.shared.logout() + }) } } .frame(minHeight: grr.size.height - 50) @@ -90,8 +93,7 @@ struct MentorProfileScreen: View { ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) .background(.white) } - } - else { + } else { if scrollAtTop { Rectangle() .frame(height: 10) @@ -128,7 +130,7 @@ extension MentorProfileScreen { .font(Font.custom("Metropolis-Regular", size: 34, relativeTo: .largeTitle)) } } - + private var description: some View { Group { HStack { @@ -152,7 +154,7 @@ extension MentorProfileScreen { .padding(.bottom, 6) } } - + private var about: some View { Group { Text("About") @@ -172,7 +174,7 @@ extension MentorProfileScreen { .padding(.bottom, 8) } } - + private var mentees: some View { Group { Text("My Mentees") @@ -202,12 +204,20 @@ extension MentorProfileScreen { struct MentorProfileScreen_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor) + CurrentUserModel.shared.setCurrentUser( + isLoading: false, + isLoggedIn: true, + uid: "6431b9a2bcf4420fe9825fe5", + role: .mentor + ) return CustomNavView { MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") .onAppear { Task { - try await FirebaseAuthenticationService.shared.login(email: "mentor@gmail.com", password: "123456") + try await FirebaseAuthenticationService.shared.login( + email: "mentor@gmail.com", + password: "123456" + ) } } } diff --git a/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift index c312d019..48a2e404 100644 --- a/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift @@ -22,7 +22,7 @@ struct MissedSessionScreen: View { var date: String var time: String var otherUser: String - + @Environment(\.dismiss) var dismiss @State var validated: Bool = true @State var selectedOption: OptionType? @@ -30,7 +30,6 @@ struct MissedSessionScreen: View { @State var otherEmpty: Bool = false @State var otherText: String = "" - var body: some View { VStack { ScrollView { @@ -180,6 +179,12 @@ struct MissedSessionScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - MissedSessionScreen(viewModel: viewModel, notesID: "646a6e164082520f4fcf2f92", date: "9/23", time: "10:00 AM", otherUser: "Mentor") + MissedSessionScreen( + viewModel: viewModel, + notesID: "646a6e164082520f4fcf2f92", + date: "9/23", + time: "10:00 AM", + otherUser: "Mentor" + ) } } diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift index b7c5167b..82e532f6 100644 --- a/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift @@ -63,7 +63,7 @@ struct PostSessionConfirmationScreen: View { Text("Save") } .buttonStyle(FilledInButtonStyle()) - + NavigationLink(destination: ConfirmationScreen( text: ["Post-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift index e8188b57..84c5356d 100644 --- a/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift @@ -15,16 +15,16 @@ struct PostSessionFormRouter: View { var otherName: String var date: String var time: String - + var body: some View { loadingAbstraction .customNavBarItems( - title: "\(date) Post-session Notes", - isPurple: false, + title: "\(date) Post-session Notes", + isPurple: false, backButtonHidden: false ) } - + var loadingAbstraction: some View { return Group { if viewModel.isLoading { @@ -33,7 +33,7 @@ struct PostSessionFormRouter: View { Task { do { try await viewModel.fetchPostSessionNotes( - notesId: notesID, + notesId: notesID, otherNotesId: otherNotesId ) } catch { @@ -46,7 +46,7 @@ struct PostSessionFormRouter: View { } } } - + var loadedView: some View { print(viewModel.questionList) return VStack { @@ -55,7 +55,13 @@ struct PostSessionFormRouter: View { .padding() .background(Color.white) if viewModel.currentIndex < viewModel.questionList.count { - PostSessionQuestionScreen(viewModel: viewModel, otherUser: otherName, date: date, time: time, noteId: notesID) + PostSessionQuestionScreen( + viewModel: viewModel, + otherUser: otherName, + date: date, + time: time, + noteId: notesID + ) } else { PostSessionConfirmationScreen(viewModel: viewModel, notesID: notesID) } @@ -67,13 +73,12 @@ struct PostSessionFormRouter_Previews: PreviewProvider { static var previews: some View { CustomNavView { PostSessionFormRouter( - notesID: "646c8ea5004999f332c55f84", + notesID: "646c8ea5004999f332c55f84", otherNotesId: "646c8ea5004999f332c55f86", - otherName: "Mentor", - date: "5/23", + otherName: "Mentor", + date: "5/23", time: "9pm" ) } } } - diff --git a/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift index bf98f1a9..674fd927 100644 --- a/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift @@ -28,14 +28,13 @@ struct PostSessionQuestionScreen: View { .edgesIgnoringSafeArea(.bottom) } - var content: some View { var currentIndex = viewModel.currentIndex if viewModel.currentIndex >= viewModel.questionList.count { currentIndex = viewModel.questionList.count - 1 } let currentQuestion = viewModel.questionList[currentIndex] - + return VStack { if currentIndex == 0 { ZStack { @@ -48,9 +47,9 @@ struct PostSessionQuestionScreen: View { .padding(.init(top: 8, leading: 16, bottom: 8, trailing: 16)) Spacer() NavigationLink( - destination: + destination: MissedSessionScreen( - viewModel: viewModel, + viewModel: viewModel, notesID: noteId, date: date, time: time, @@ -122,7 +121,7 @@ extension PostSessionQuestionScreen { .padding(.bottom, 40) .background(Rectangle().fill(Color.white).shadow(radius: 8)) } - + var footerForFirstQuestion: some View { Button { viewModel.nextQuestion() @@ -136,7 +135,7 @@ extension PostSessionQuestionScreen { } .buttonStyle(FilledInButtonStyle()) } - + var footerWithBackAndContinue: some View { HStack { Button { @@ -166,10 +165,10 @@ extension PostSessionQuestionScreen { } } // -//struct PostSessionQuestionScreen_Previews: PreviewProvider { +// struct PostSessionQuestionScreen_Previews: PreviewProvider { // static private var viewModel = QuestionViewModel() // // static var previews: some View { // PostSessionQuestionScreen(viewModel: viewModel) // } -//} +// } diff --git a/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift b/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift index 6cb95f69..b6307b23 100644 --- a/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift +++ b/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift @@ -15,9 +15,8 @@ struct ViewPostSessionNotesPage: View { var otherName: String var date: String var time: String - - @State var currNotes: String = "this" // "this" or "other" + @State var currNotes: String = "this" // "this" or "other" func setMyNotes() { currNotes = "this" @@ -43,9 +42,9 @@ struct ViewPostSessionNotesPage: View { Task { do { try await viewModel.fetchPostSessionNotes( - notesId: notesID, + notesId: notesID, otherNotesId: otherNotesID - ) + ) } catch { print("Error") } @@ -69,21 +68,21 @@ struct ViewPostSessionNotesPage: View { .edgesIgnoringSafeArea(.bottom) } var footer: some View { - return CustomNavLink ( - destination: + return CustomNavLink( + destination: PostSessionFormRouter( - notesID: notesID, + notesID: notesID, otherNotesId: otherNotesID, - otherName: otherName, - date: date, + otherName: otherName, + date: date, time: time - ), + ), label: { HStack { Image(systemName: "pencil.line") Text("Edit") } - }) + }) .buttonStyle(FilledInButtonStyle()) } @@ -206,6 +205,12 @@ struct ViewPostSessionNotesPage: View { struct ViewPostSessionNotesPage_Previews: PreviewProvider { static var previews: some View { - ViewPostSessionNotesPage(notesID: "646c8ea5004999f332c55f84", otherNotesID: "646c8ea5004999f332c55f86", otherName: "Mentor", date: "5/23", time: "9:30 AM") + ViewPostSessionNotesPage( + notesID: "646c8ea5004999f332c55f84", + otherNotesID: "646c8ea5004999f332c55f86", + otherName: "Mentor", + date: "5/23", + time: "9:30 AM" + ) } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift index ade74ef5..c22bf025 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift @@ -54,7 +54,8 @@ struct PreSessionConfirmationScreen: View { ALUMText(text: "Save", textColor: ALUMColor.white) } .buttonStyle(FilledInButtonStyle()) - // TODO change this to custom nav link + + // Custom Nav Link not needed here NavigationLink(destination: ConfirmationScreen( text: ["Pre-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift index 89d18e7e..e87da5c0 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift @@ -14,16 +14,16 @@ struct PreSessionFormRouter: View { var otherName: String var date: String var time: String - + var body: some View { loadingAbstraction .customNavBarItems( - title: "\(date) Pre-session Notes", - isPurple: false, + title: "\(date) Pre-session Notes", + isPurple: false, backButtonHidden: false ) } - + var loadingAbstraction: some View { print("isLoading \(viewModel.isLoading)") return Group { @@ -43,7 +43,7 @@ struct PreSessionFormRouter: View { } } } - + var loadedView: some View { return VStack { DynamicProgressBarComponent(nodes: $viewModel.questionList.count + 1, diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift index 61414783..e240d30b 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift @@ -25,15 +25,12 @@ struct PreSessionQuestionScreen: View { .edgesIgnoringSafeArea(.bottom) } - - var content: some View { var currentIndex = viewModel.currentIndex if viewModel.currentIndex >= viewModel.questionList.count { currentIndex = viewModel.questionList.count - 1 } let currentQuestion = viewModel.questionList[currentIndex] - return VStack { if viewModel.currentIndex == 0 { @@ -54,13 +51,13 @@ struct PreSessionQuestionScreen: View { .padding(.leading, 16) .padding(.bottom, 16) .padding(.top, 8) - + BulletsView(bullets: $viewModel.questionList[currentIndex].answerBullet, question: currentQuestion.question) } } } - + var firstQuestionBanner: some View { ZStack { RoundedRectangle(cornerRadius: 12) @@ -93,7 +90,7 @@ extension PreSessionQuestionScreen { .padding(.bottom, 40) .background(Rectangle().fill(Color.white).shadow(radius: 8)) } - + var footerForFirstQuestion: some View { Button { viewModel.nextQuestion() @@ -107,7 +104,7 @@ extension PreSessionQuestionScreen { } .buttonStyle(FilledInButtonStyle()) } - + var footerWithBackAndContinue: some View { HStack { Button { diff --git a/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift index cc686289..2a78447d 100644 --- a/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift +++ b/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift @@ -10,17 +10,17 @@ import SwiftUI struct ViewPreSessionNotesPage: View { var allowEditing: Bool var notesID: String - var otherName: String + var otherName: String var date: String = "" var time: String = "" - + @StateObject var viewModel = QuestionViewModel() var body: some View { loadingAbstraction .customNavBarItems(title: "\(date) Pre-session Notes", isPurple: false, backButtonHidden: false) } - + var loadingAbstraction: some View { Group { if !viewModel.isLoading { @@ -39,13 +39,13 @@ struct ViewPreSessionNotesPage: View { } } } - + var loadedView: some View { VStack { ScrollView { content } - + if allowEditing { footer .padding(.horizontal, 16) @@ -58,21 +58,20 @@ struct ViewPreSessionNotesPage: View { } var footer: some View { - // TODO change navigation to pre-session router - return CustomNavLink ( - destination: + return CustomNavLink( + destination: PreSessionFormRouter( - notesID: notesID, - otherName: otherName, - date: date, + notesID: notesID, + otherName: otherName, + date: date, time: time - ), + ), label: { HStack { Image(systemName: "pencil.line") Text("Edit") } - }) + }) .buttonStyle(FilledInButtonStyle()) } diff --git a/ALUM/ALUM/Views/Routers/HomeTabRouter.swift b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift index d27bb192..30d5a1b7 100644 --- a/ALUM/ALUM/Views/Routers/HomeTabRouter.swift +++ b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift @@ -7,7 +7,8 @@ import SwiftUI -/// For MVP, our home tab is either a session details page or just a screen with pairing info and button to book a session +/// For MVP, our home tab is either a session details page or just a screen +/// with pairing info and button to book a session struct HomeTabRouter: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared diff --git a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift index 13afeb7c..94036e6a 100644 --- a/ALUM/ALUM/Views/Routers/LoggedInRouter.swift +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -12,7 +12,7 @@ struct LoggedInRouter: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared @State private var selection: Int - + init(defaultSelection: Int = 0) { UITabBar.appearance().backgroundColor = UIColor(Color.white) // custom color. selection = defaultSelection @@ -22,13 +22,15 @@ struct LoggedInRouter: View { TabBarItem(iconName: "ALUM Home", title: "Home"), TabBarItem(iconName: "GrayCircle", title: "Profile") ] - + // once user is approved and paired var body: some View { return ZStack(alignment: .bottom) { - // Turns out for preference keys to work you need to run the mutate preference key function from either the view directly + // Turns out for preference keys to work you need to run the mutate preference + // key function from either the view directly // inside the navigation view OR from an inner view which is traversed last. - // Swift runs the preference key functions in a DFS manner. That's why we had to display the tab bar this way so that content is + // Swift runs the preference key functions in a DFS manner. + // That's why we had to display the tab bar this way so that content is // the last item traversed VStack { Spacer() @@ -40,23 +42,24 @@ struct LoggedInRouter: View { .padding(.bottom, 50) } } - + var content: some View { VStack(spacing: 0) { - + Group { switch selection { case 0: HomeTabRouter() case 1: ProfileTabRouter() - .customNavigationIsHidden(true) // We shall use our own header component here so that we can easily add the edit buttons + .customNavigationIsHidden(true) + // We shall use our own header component here so that we can easily add the edit buttons default: Text("Error") } - + } - .onAppear(perform: { + .onAppear(perform: { currentUser.showTabBar = true }) .onDisappear(perform: { @@ -64,7 +67,7 @@ struct LoggedInRouter: View { }) } } - + var tabsDisplay: some View { ZStack(alignment: .bottom) { HStack(spacing: 0) { diff --git a/ALUM/ALUM/Views/Routers/RootRouter.swift b/ALUM/ALUM/Views/Routers/RootRouter.swift index c9fa3a60..f4ac13b3 100644 --- a/ALUM/ALUM/Views/Routers/RootRouter.swift +++ b/ALUM/ALUM/Views/Routers/RootRouter.swift @@ -34,10 +34,18 @@ struct RootRouter: View { struct RootView_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: true, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentor) + CurrentUserModel.shared.setCurrentUser( + isLoading: true, + isLoggedIn: true, + uid: "6431b9a2bcf4420fe9825fe5", + role: .mentor + ) return RootRouter().onAppear(perform: { Task { - try await CurrentUserModel.shared.fetchUserInfoFromServer(userId: "6431b9a2bcf4420fe9825fe5", role: .mentor) + try await CurrentUserModel.shared.fetchUserInfoFromServer( + userId: "6431b9a2bcf4420fe9825fe5", + role: .mentor + ) } }) } diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index a4cfa2a6..6c10a6ba 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -22,7 +22,7 @@ struct SessionDetailsScreen: View { return loadingAbstraction .customNavigationIsPurple(false) } - + var loadingAbstraction: some View { Group { if viewModel.isLoading || viewModel.session == nil { @@ -45,7 +45,7 @@ struct SessionDetailsScreen: View { var navigationBarConfig: some View { let session = viewModel.session! var otherName: String - + switch currentUser.role { case .mentee: otherName = session.mentorName @@ -53,28 +53,28 @@ struct SessionDetailsScreen: View { otherName = session.menteeName case .none: otherName = "" - // TODO Internal error + // (todo) Internal error } - + return ScrollView { screenContent } .customNavigationTitle("\(session.dateShortHandString) Session with \(otherName)") .background(ALUMColor.beige.color) } - + var screenContent: some View { let session = viewModel.session! - + return VStack(alignment: .leading) { if currentUser.role == .mentee { menteeView } else { mentorView - } + } if !session.hasPassed { Button { - + } label: { ALUMText(text: "Cancel Session", textColor: ALUMColor.red) } @@ -88,9 +88,7 @@ struct SessionDetailsScreen: View { .padding(.top, 28) } - - - + // Section which displays the date and time var dateTimeDisplaySection: some View { let session = viewModel.session! @@ -114,12 +112,11 @@ struct SessionDetailsScreen: View { } .padding(.bottom, 20) } - + var bookSessionButton: some View { let session = viewModel.session! let buttonDisabled: Bool = !session.postSessionMenteeCompleted - return Button { showCalendlyWebView = true } label: { @@ -132,12 +129,10 @@ struct SessionDetailsScreen: View { .buttonStyle(FilledInButtonStyle(disabled: buttonDisabled)) .padding(.bottom, 26) } - - - + var locationSectionForAny: some View { let session = viewModel.session! - + return Group { HStack { ALUMText(text: "Location", textColor: ALUMColor.gray4) @@ -152,16 +147,16 @@ struct SessionDetailsScreen: View { .padding(.bottom, 20) } } - + var postSessionNotesSectionForAny: some View { let session = viewModel.session! var formIsComplete: Bool, editableNoteId: String, otherNoteId: String, otherName: String - + if session.postSessionMentee == nil || session.postSessionMentor == nil { print("Attempting to display session view but no post session notes IDs present ") - // TODO internal error + // (todo) internal error } - + switch currentUser.role { case .mentee: formIsComplete = session.postSessionMenteeCompleted @@ -179,15 +174,15 @@ struct SessionDetailsScreen: View { otherNoteId = "" otherName = "" } - + return Group { HStack { ALUMText(text: "Post-Session Form", textColor: ALUMColor.gray4) - + Spacer() } .padding(.bottom, formIsComplete ? 20 : 5) - + if !formIsComplete { HStack { FormIncompleteComponent(type: "Post") @@ -195,16 +190,16 @@ struct SessionDetailsScreen: View { } .padding(.bottom, 22) } - + if !formIsComplete { CustomNavLink( destination: PostSessionFormRouter( notesID: editableNoteId, otherNotesId: otherNoteId, - otherName: otherName, - date: session.dateShortHandString, + otherName: otherName, + date: session.dateShortHandString, time: session.startTimeString - ), + ), label: { ALUMText(text: "Complete Post-Session Notes", textColor: ALUMColor.white) } @@ -219,7 +214,7 @@ struct SessionDetailsScreen: View { otherName: otherName, date: session.dateShortHandString, time: session.startTimeString - ), + ), label: { ALUMText(text: "View Post-Session Notes", textColor: ALUMColor.white) } @@ -229,7 +224,7 @@ struct SessionDetailsScreen: View { } } } - + var viewPreSessionNotesForAny: some View { let session = viewModel.session! var otherName: String @@ -243,8 +238,8 @@ struct SessionDetailsScreen: View { } return CustomNavLink( destination: ViewPreSessionNotesPage( - allowEditing: false, - notesID: session.preSession, + allowEditing: false, + notesID: session.preSession, otherName: otherName ), label: { @@ -254,16 +249,19 @@ struct SessionDetailsScreen: View { .buttonStyle(OutlinedButtonStyle()) .padding(.bottom, 5) } - + var afterEventSectionForAny: some View { let session = viewModel.session! - + return VStack { if session.missedSessionReason == nil { postSessionNotesSectionForAny } else { HStack { - ALUMText(text: "Session was not completed due to: \(session.missedSessionReason!)", textColor: ALUMColor.red) + ALUMText( + text: "Session was not completed due to: \(session.missedSessionReason!)", + textColor: ALUMColor.red + ) Spacer() } .padding(.bottom, 10) @@ -284,9 +282,9 @@ extension SessionDetailsScreen { Spacer() } .padding(.bottom, 5) - + CustomNavLink( - destination: + destination: MenteeProfileScreen( uID: session.menteeId ).customNavigationTitle("Mentee Profile") @@ -305,29 +303,29 @@ extension SessionDetailsScreen { } } } - + var beforeEventSectionMentor: some View { return Group { locationSectionForAny preSessionNotesSectionForMentor } } - + var preSessionNotesSectionForMentor: some View { // Mentor can view mentee's pre-session notes let session = viewModel.session! - + return Group { HStack { ALUMText(text: "Pre-Session Form", textColor: ALUMColor.gray4) Spacer() } .padding(.bottom, 20) - + CustomNavLink( destination: ViewPreSessionNotesPage( - allowEditing: false, - notesID: session.preSession, + allowEditing: false, + notesID: session.preSession, otherName: session.menteeName ), label: { ALUMText(text: "View Pre-Session Form", textColor: ALUMColor.white) @@ -345,14 +343,14 @@ extension SessionDetailsScreen { return Group { bookSessionButton - + HStack { ALUMText(text: "Mentor", textColor: ALUMColor.gray4) Spacer() } .padding(.bottom, 5) - - CustomNavLink(destination: + + CustomNavLink(destination: MentorProfileScreen(uID: session.mentorId) .customNavigationTitle("Mentor Profile") ) { @@ -360,7 +358,7 @@ extension SessionDetailsScreen { .padding(.bottom, 28) } dateTimeDisplaySection - + if session.hasPassed { afterEventSectionForAny } else { @@ -369,7 +367,7 @@ extension SessionDetailsScreen { } } - + var beforeEventSectionMentee: some View { return Group { locationSectionForAny @@ -383,7 +381,7 @@ extension SessionDetailsScreen { preSessionNotesSectionForMentee } } - + var preSessionNotesSectionForMentee: some View { let session = viewModel.session! @@ -403,15 +401,15 @@ extension SessionDetailsScreen { } if !session.preSessionCompleted { - + CustomNavLink( - destination: + destination: PreSessionFormRouter( - notesID: session.preSession, - otherName: session.mentorName, - date: session.dateShortHandString, + notesID: session.preSession, + otherName: session.mentorName, + date: session.dateShortHandString, time: session.startTimeString - ), + ), label: { ALUMText(text: "Complete Pre-Session Notes", textColor: ALUMColor.white) } @@ -437,8 +435,13 @@ extension SessionDetailsScreen { } struct SessionDetailsScreen_Previews: PreviewProvider { static var previews: some View { - CurrentUserModel.shared.setCurrentUser(isLoading: false, isLoggedIn: true, uid: "6431b9a2bcf4420fe9825fe5", role: .mentee) - + CurrentUserModel.shared.setCurrentUser( + isLoading: false, + isLoggedIn: true, + uid: "6431b9a2bcf4420fe9825fe5", + role: .mentee + ) + return CustomNavView { SessionDetailsScreen(sessionId: "6464276b6f05d9703f069760") } From 4e50bfe2e2d940fe3a0d661993ec9e7eb0d27c56 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 22:47:15 -0700 Subject: [PATCH 26/33] manually disable swiftlint file_length for SessionDetailsScreen --- ALUM/ALUM/Views/SessionDetailsScreen.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index 6c10a6ba..d0b2f869 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -5,6 +5,8 @@ // Created by Aman Aggarwal on 5/22/23. // +// swiftlint:disable file_length + import SwiftUI let isMVP: Bool = true @@ -447,3 +449,5 @@ struct SessionDetailsScreen_Previews: PreviewProvider { } } } + +// swiftlint:enable file_length From e236a9f4ae028c602540d38f2b4f4cb967e6a81e Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sat, 27 May 2023 22:50:23 -0700 Subject: [PATCH 27/33] fix backend lint --- backend/src/errors/internal.ts | 13 +++--- backend/src/errors/service.ts | 4 +- backend/src/models/mentor.ts | 1 + backend/src/routes/notes.ts | 2 +- backend/src/routes/sessions.ts | 10 ++--- backend/src/routes/user.ts | 74 +++++++++++++++++++-------------- backend/src/services/session.ts | 51 +++++++++++++---------- backend/src/services/user.ts | 1 - backend/src/types/user.ts | 3 +- 9 files changed, 91 insertions(+), 68 deletions(-) diff --git a/backend/src/errors/internal.ts b/backend/src/errors/internal.ts index aeea2d11..08431286 100644 --- a/backend/src/errors/internal.ts +++ b/backend/src/errors/internal.ts @@ -15,8 +15,8 @@ const NO_DEFAULT_IMAGE_ID = "Could not find default image id env variable"; const ERROR_ROLES_NOT_MENTOR_MENTEE_NOT_IMPLEMENTED = "Any roles other than mentor/mentee has not been implemented."; const ERROR_FINDING_PAIR = "There was an error getting the mentee/mentor pairing"; -const ERROR_FINDING_UPCOMING_SESSION = "Error occured while finding some upcoming session" -const ERROR_FINDING_PAST_SESSION = "Error occured while finding some past session" +const ERROR_FINDING_UPCOMING_SESSION = "Error occured while finding some upcoming session"; +const ERROR_FINDING_PAST_SESSION = "Error occured while finding some past session"; export class InternalError extends CustomError { static ERROR_GETTING_MENTEE = new InternalError(0, 500, ERROR_GETTING_MENTEE); @@ -42,9 +42,12 @@ export class InternalError extends CustomError { ); static ERROR_FINDING_PAIR = new InternalError(9, 500, ERROR_FINDING_PAIR); - - static ERROR_FINDING_UPCOMING_SESSION = new InternalError(10, 500, ERROR_FINDING_UPCOMING_SESSION); - static ERROR_FINDING_PAST_SESSION = new InternalError(11, 500, ERROR_FINDING_PAST_SESSION); + static ERROR_FINDING_UPCOMING_SESSION = new InternalError( + 10, + 500, + ERROR_FINDING_UPCOMING_SESSION + ); + static ERROR_FINDING_PAST_SESSION = new InternalError(11, 500, ERROR_FINDING_PAST_SESSION); } diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 8039217e..6dece9ad 100644 --- a/backend/src/errors/service.ts +++ b/backend/src/errors/service.ts @@ -17,7 +17,7 @@ const NOTE_WAS_NOT_SAVED = "Note was not saved"; const SESSION_WAS_NOT_FOUND = "Session was not found"; const INVALID_URI = "Calendly URI is invalid. Check formatting of URI string"; const ERROR_GETTING_EVENT_DATA = "There was an error retrieving the calendly event data"; -const INVALID_ROLE_WAS_FOUND = "Allowed user roles for this context is mentor and mentee only" +const INVALID_ROLE_WAS_FOUND = "Allowed user roles for this context is mentor and mentee only"; export class ServiceError extends CustomError { static IMAGE_NOT_SAVED = new ServiceError(0, 404, IMAGE_NOT_SAVED); @@ -40,6 +40,6 @@ export class ServiceError extends CustomError { static NOTE_WAS_NOT_FOUND = new ServiceError(9, 404, NOTE_WAS_NOT_FOUND); static NOTE_WAS_NOT_SAVED = new ServiceError(10, 404, NOTE_WAS_NOT_SAVED); - + static INVALID_ROLE_WAS_FOUND = new ServiceError(11, 404, INVALID_ROLE_WAS_FOUND); } diff --git a/backend/src/models/mentor.ts b/backend/src/models/mentor.ts index 1f992759..7e57e63f 100644 --- a/backend/src/models/mentor.ts +++ b/backend/src/models/mentor.ts @@ -5,6 +5,7 @@ */ import mongoose from "mongoose"; import { UserStatusType } from "../types"; + interface MentorInterface { name: string; imageId: string; diff --git a/backend/src/routes/notes.ts b/backend/src/routes/notes.ts index 5db300d4..f89b9f00 100644 --- a/backend/src/routes/notes.ts +++ b/backend/src/routes/notes.ts @@ -22,7 +22,7 @@ router.get("/notes/:id", async (req: Request, res: Response, next: NextFunction) try { const id = req.params.id; const note = await Note.findById(id); - console.log(`GETTING note - ID ${note?.id}`) + console.log(`GETTING note - ID ${note?.id}`); if (note == null) throw ServiceError.NOTE_WAS_NOT_FOUND; if (note.type === "post") { const temp = await Session.findById(note.session); diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 71af7a76..ee376a10 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -13,8 +13,7 @@ import { CreateSessionRequestBodyCake } from "../types/cakes"; import { InternalError, ServiceError } from "../errors"; import { getCalendlyEventDate } from "../services/calendly"; import { getMentorId } from "../services/user"; -import {formatDateTimeRange} from "../services/session"; - +import { formatDateTimeRange } from "../services/session"; /** * This is a post route to create a new session. @@ -136,10 +135,11 @@ router.get( postSessionMenteeCompleted, postSessionMentorCompleted, } = session; - const [fullDateString, dateShortHandString, startTimeString, endTimeString]= formatDateTimeRange(startTime, endTime); + const [fullDateString, dateShortHandString, startTimeString, endTimeString] = + formatDateTimeRange(startTime, endTime); const hasPassed = dateNow.getTime() - endTime.getTime() > 0; - + return res.status(200).send({ message: `Here is session ${sessionId}`, session: { @@ -159,7 +159,7 @@ router.get( postSessionMenteeCompleted, postSessionMentorCompleted, hasPassed, - location: mentor.location + location: mentor.location, }, }); } catch (e) { diff --git a/backend/src/routes/user.ts b/backend/src/routes/user.ts index 5d430baf..74059fc9 100644 --- a/backend/src/routes/user.ts +++ b/backend/src/routes/user.ts @@ -368,11 +368,11 @@ router.get( /** * Route to setup mobile app for any logged in user (mentor or mentee) - * + * * This route returns the following - * If user is a mentor, + * If user is a mentor, * menteeIds, status, upcomingSessionId - * + * * If user is mentee, * mentorId, status, upcomingSessionId */ @@ -383,57 +383,67 @@ router.get( try { const userId = req.body.uid; const role = req.body.role; - - const getUpcomingSessionPromise = getUpcomingSession(userId, role) - const getPastSessionPromise = getLastSession(userId, role) - if (role == "mentee") { + + const getUpcomingSessionPromise = getUpcomingSession(userId, role); + const getPastSessionPromise = getLastSession(userId, role); + if (role === "mentee") { // GET mentee document const mentee = await Mentee.findById(userId); if (!mentee) { throw ServiceError.MENTEE_WAS_NOT_FOUND; } - - if (mentee.status != "paired") { + + if (mentee.status !== "paired") { res.status(200).send({ - status: mentee.status - }) + status: mentee.status, + }); } - const getPairedMentorIdPromise = getMentorId(mentee.pairingId) - const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([getUpcomingSessionPromise, getPastSessionPromise, getPairedMentorIdPromise]) + const getPairedMentorIdPromise = getMentorId(mentee.pairingId); + const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getPairedMentorIdPromise, + ]); console.log({ status: mentee.status, sessionId: upcomingSessionId ?? pastSessionId, - pairedMentorId - }) + pairedMentorId, + }); res.status(200).send({ status: mentee.status, sessionId: upcomingSessionId ?? pastSessionId, - pairedMentorId - }) - } else if (role == "mentor") { + pairedMentorId, + }); + } else if (role === "mentor") { const mentor = await Mentor.findById(userId); if (!mentor) { throw ServiceError.MENTOR_WAS_NOT_FOUND; } - - if (mentor.status != "paired") { + + if (mentor.status !== "paired") { res.status(200).send({ - status: mentor.status - }) + status: mentor.status, + }); } - - const getMenteeIdsPromises = mentor.pairingIds.map(async (pairingId) => getMenteeId(pairingId)); - + + const getMenteeIdsPromises = mentor.pairingIds.map(async (pairingId) => + getMenteeId(pairingId) + ); + // For MVP, we assume there is only 1 mentee 1 mentor pairing - const getMenteeIdsPromise = getMenteeIdsPromises[0] - - const [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([getUpcomingSessionPromise, getPastSessionPromise, getMenteeIdsPromise]) - + const getMenteeIdsPromise = getMenteeIdsPromises[0]; + + const [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getMenteeIdsPromise, + ]); + res.status(200).send({ status: mentor.status, sessionId: upcomingSessionId ?? pastSessionId, - pairedMenteeId - }) + pairedMenteeId, + }); } } catch (e) { if (e instanceof CustomError) { @@ -443,6 +453,6 @@ router.get( next(InternalError.ERROR_GETTING_MENTEE); } } -) +); export { router as userRouter }; diff --git a/backend/src/services/session.ts b/backend/src/services/session.ts index 3721d2e3..e38c43f2 100644 --- a/backend/src/services/session.ts +++ b/backend/src/services/session.ts @@ -1,18 +1,21 @@ -import { Types } from 'mongoose'; +import { Types } from "mongoose"; import { InternalError, ServiceError } from "../errors"; -import { Session, SessionDoc } from '../models'; +import { Session } from "../models"; -export async function getUpcomingSession(userId: string, role: 'mentor' | 'mentee'): Promise { +export async function getUpcomingSession( + userId: string, + role: "mentor" | "mentee" +): Promise { const now = new Date(); let matchField: string; - if (role === 'mentor') { - matchField = 'mentorId'; - } else if (role === 'mentee') { - matchField = 'menteeId'; + if (role === "mentor") { + matchField = "mentorId"; + } else if (role === "mentee") { + matchField = "menteeId"; } else { - throw ServiceError.INVALID_ROLE_WAS_FOUND + throw ServiceError.INVALID_ROLE_WAS_FOUND; } try { @@ -23,19 +26,22 @@ export async function getUpcomingSession(userId: string, role: 'mentor' | 'mente return upcomingSession?._id; } catch (error) { - throw InternalError.ERROR_FINDING_UPCOMING_SESSION + throw InternalError.ERROR_FINDING_UPCOMING_SESSION; } } -export async function getLastSession(userId: string, role: 'mentor' | 'mentee'): Promise { +export async function getLastSession( + userId: string, + role: "mentor" | "mentee" +): Promise { const now = new Date(); let matchField: string; - if (role === 'mentor') { - matchField = 'mentorId'; - } else if (role === 'mentee') { - matchField = 'menteeId'; + if (role === "mentor") { + matchField = "mentorId"; + } else if (role === "mentee") { + matchField = "menteeId"; } else { throw ServiceError.INVALID_ROLE_WAS_FOUND; } @@ -61,7 +67,10 @@ export async function getLastSession(userId: string, role: 'mentor' | 'mentee'): * "2:30 PM" * ] */ -export function formatDateTimeRange(startTime: Date, endTime: Date): [string, string, string, string] { +export function formatDateTimeRange( + startTime: Date, + endTime: Date +): [string, string, string, string] { const dateOptions: Intl.DateTimeFormatOptions = { weekday: "long", year: "numeric", @@ -86,18 +95,18 @@ export function formatDateTimeRange(startTime: Date, endTime: Date): [string, st const endTimeString = endTime.toLocaleTimeString("en-US", timeOptions); const dateShortHandString = startTime.toLocaleDateString("en-US", dateShortHandOptions); - let fullDateString + let fullDateString; if (startDateString === endDateString) { // Thursday, April 27, 2023 at 2:00 PM - 2:30 PM - fullDateString = startDateString + fullDateString = startDateString; } else { - fullDateString = endDateString + fullDateString = endDateString; } - - return [fullDateString, dateShortHandString, startTimeString, endTimeString] + + return [fullDateString, dateShortHandString, startTimeString, endTimeString]; // Thursday, April 27, 2023 at 2:00 PM - Friday, April 28, 2023 at 2:30 PM } const startDate = new Date(); const endDate = new Date(); -console.log(formatDateTimeRange(startDate, endDate)) \ No newline at end of file +console.log(formatDateTimeRange(startDate, endDate)); diff --git a/backend/src/services/user.ts b/backend/src/services/user.ts index 7b90fe16..44fb012a 100644 --- a/backend/src/services/user.ts +++ b/backend/src/services/user.ts @@ -42,5 +42,4 @@ async function getMenteeId(pairingId: string): Promise { return pairing.menteeId; } - export { getMentorId, getMenteeId }; diff --git a/backend/src/types/user.ts b/backend/src/types/user.ts index 9adbd5a7..13be56d0 100644 --- a/backend/src/types/user.ts +++ b/backend/src/types/user.ts @@ -1,5 +1,6 @@ import { Infer } from "caketype"; import { CreateMenteeRequestBodyCake, CreateMentorRequestBodyCake } from "./cakes"; -export type UserStatusType = "paired" | "under review" | "approved" + +export type UserStatusType = "paired" | "under review" | "approved"; export type CreateMenteeRequestBodyType = Infer; export type CreateMentorRequestBodyType = Infer; From 761fbe4bf400da48fc48bc6253cc4b3df5d03ec8 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 28 May 2023 11:52:37 -0700 Subject: [PATCH 28/33] remove SessionDetailsView cauz it was added during merge conflict fix --- .../MenteeSessionsDetailsPage.swift | 274 ------------------ .../MentorSessionDetailsPage.swift | 247 ---------------- .../SessionDetailsPageView.swift | 179 ------------ 3 files changed, 700 deletions(-) delete mode 100644 ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift delete mode 100644 ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift delete mode 100644 ALUM/ALUM/Views/SessionDetailsView/SessionDetailsPageView.swift diff --git a/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift b/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift deleted file mode 100644 index 811e0c61..00000000 --- a/ALUM/ALUM/Views/SessionDetailsView/MenteeSessionsDetailsPage.swift +++ /dev/null @@ -1,274 +0,0 @@ -// -// MenteeSessionsDetailsPage.swift -// ALUM -// -// Created by Neelam Gurnani on 4/13/23. -// - -import SwiftUI - -struct MenteeSessionDetailsHeaderModifier: ViewModifier { - @State var date: String = "" - @State var mentor: String = "" - - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Session with \(mentor)", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyMenteeSessionDetailsHeaderModifier(date: String, mentor: String) -> some View { - self.modifier(MenteeSessionDetailsHeaderModifier(date: date, mentor: mentor)) - } -} - -struct MenteeSessionsDetailsPage: View { - @StateObject private var viewModel = SessionDetailViewModel() - - var body: some View { - Group { - if !viewModel.isLoading { - NavigationView { - GeometryReader { grr in - VStack { - ScrollView { - content - .padding(.horizontal, 16) - } - .frame(minHeight: grr.size.height-120) - - NavigationFooter(page: "Home") - } - .applyMenteeSessionDetailsHeaderModifier( - date: viewModel.session.date, - mentor: viewModel.session.mentor.mentor.name) - .edgesIgnoringSafeArea(.bottom) - } - } - } else { - ProgressView() - } - } - .onAppear { - Task { - do { - var sessionsArray: [UserSessionInfo] = try await SessionService().getSessionsByUser().sessions - - try await viewModel.loadSession(sessionID: sessionsArray[0].id) - } catch { - print(error) - } - } - } - - } - - var content: some View { - VStack { - Group { - HStack { - Text("Mentor") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.top, 28) - .padding(.bottom, 20) - - NavigationLink(destination: MentorProfileScreen(uID: viewModel.session.mentor.mentor.id)) { - MentorCard(isEmpty: true, uID: viewModel.session.mentor.mentor.id) - .padding(.bottom, 28) - } - } - - Group { - HStack { - Text("Date & Time") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.day + ", " + viewModel.session.date) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.startTime + " - " + viewModel.session.endTime) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 20) - } - - if !viewModel.sessionCompleted { - /* - Button { - - } label: { - Text("Reschedule Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 20) - */ - - Group { - HStack { - Text("Location") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.mentor.mentor.zoomLink ?? "zoom.com") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Dark Blue")) - - Spacer() - } - .padding(.bottom, 20) - } - - Group { - HStack { - Text("Pre-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Pre") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PreSessionView( - notesID: viewModel.session.preSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPreSessionNotesPage( - notesID: viewModel.session.preSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - - Button { - - } label: { - Text("Cancel Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("FunctionalError")) - } - .buttonStyle(OutlinedButtonStyle()) - .border(Color("FunctionalError")) - .cornerRadius(8.0) - } else { - Group { - HStack { - Text("Post-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Post") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PostSessionView( - notesID: viewModel.session.menteePostSessionID, - otherNotesID: viewModel.session.mentorPostSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPostSessionNotesPage( - notesID: viewModel.session.menteePostSessionID, - otherNotesID: viewModel.session.mentorPostSessionID, - otherName: viewModel.session.mentor.mentor.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - } - } - } -} - -struct MenteeSessionsDetailsPage_Previews: PreviewProvider { - static var previews: some View { - MenteeSessionsDetailsPage() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift b/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift deleted file mode 100644 index 566feb44..00000000 --- a/ALUM/ALUM/Views/SessionDetailsView/MentorSessionDetailsPage.swift +++ /dev/null @@ -1,247 +0,0 @@ -// -// MentorSessionDetailsPage.swift -// ALUM -// -// Created by Neelam Gurnani on 4/13/23. -// - -import SwiftUI - -struct MentorSessionDetailsHeaderModifier: ViewModifier { - @State var date: String = "" - @State var mentee: String = "" - - func body(content: Content) -> some View { - VStack { - VStack { - NavigationHeaderComponent( - backText: "", - backDestination: LoginScreen(), - title: "Session with \(mentee)", - purple: false - ) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applyMentorSessionDetailsHeaderModifier(date: String, mentee: String) -> some View { - self.modifier(MentorSessionDetailsHeaderModifier(date: date, mentee: mentee)) - } -} - -struct MentorSessionDetailsPage: View { - @StateObject private var viewModel = SessionDetailViewModel() - - var body: some View { - Group { - if !viewModel.isLoading { - NavigationView { - GeometryReader { grr in - VStack { - ScrollView { - content - .padding(.horizontal, 16) - } - .frame(minHeight: grr.size.height-120) - } - .applyMentorSessionDetailsHeaderModifier( - date: viewModel.session.date, - mentee: viewModel.session.mentee.mentee.name) - .edgesIgnoringSafeArea(.bottom) - } - } - } else { - ProgressView() - } - } - .onAppear { - Task { - do { - var sessionsArray: [UserSessionInfo] = try await SessionService().getSessionsByUser().sessions - - try await viewModel.loadSession(sessionID: sessionsArray[0].id) - } catch { - print(error) - } - } - } - } - - var content: some View { - VStack { - Group { - HStack { - Text("Mentee") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.top, 28) - .padding(.bottom, 20) - - NavigationLink(destination: MenteeProfileScreen(uID: viewModel.session.mentee.mentee.id)) { - HorizontalMenteeCard( - name: viewModel.session.mentee.mentee.name, - grade: viewModel.session.mentee.mentee.grade, - school: "NHS", - isEmpty: true - ) - .padding(.bottom, 28) - } - } - - Group { - HStack { - Text("Date & Time") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.day + ", " + viewModel.session.date) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.startTime + " - " + viewModel.session.endTime) - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 20) - } - - if !viewModel.sessionCompleted { - /* - Button { - - } label: { - Text("Reschedule Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 20) - */ - - Group { - HStack { - Text("Location") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text(viewModel.session.mentor.mentor.zoomLink ?? "zoom.com") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Dark Blue")) - - Spacer() - } - .padding(.bottom, 20) - } - - Group { - HStack { - Text("Pre-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - NavigationLink { - ViewPreSessionNotesPage(notesID: viewModel.session.preSessionID) - } label: { - Text("View Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - - Button { - - } label: { - Text("Cancel Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("FunctionalError")) - } - .buttonStyle(OutlinedButtonStyle()) - .border(Color("FunctionalError")) - .cornerRadius(8.0) - } else { - Group { - HStack { - Text("Post-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Post") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - NavigationLink { - PostSessionView( - notesID: viewModel.session.mentorPostSessionID, - otherNotesID: viewModel.session.menteePostSessionID, - otherName: viewModel.session.mentee.mentee.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("Complete Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - NavigationLink { - ViewPostSessionNotesPage( - notesID: viewModel.session.mentorPostSessionID, - otherNotesID: viewModel.session.menteePostSessionID, - otherName: viewModel.session.mentee.mentee.name, - date: viewModel.session.date, - time: viewModel.session.startTime - ) - } label: { - Text("View Post-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - } - } - } -} - -struct MentorSessionDetailsPage_Previews: PreviewProvider { - static var previews: some View { - MentorSessionDetailsPage() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsPageView.swift b/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsPageView.swift deleted file mode 100644 index 24cf0100..00000000 --- a/ALUM/ALUM/Views/SessionDetailsView/SessionDetailsPageView.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// SessionDetailsPageView.swift -// ALUM -// -// Created by Neelam Gurnani on 4/13/23. -// - -import SwiftUI - -struct SessionDetailsHeaderModifier: ViewModifier { - func body(content: Content) -> some View { - VStack { - VStack { - Text("[Date] Session with Mentor") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .frame(maxWidth: .infinity, alignment: .center) - } - content - .background(Color("ALUM White 2")) - } - } -} - -extension View { - func applySessionDetailsHeaderModifier() -> some View { - self.modifier(SessionDetailsHeaderModifier()) - } -} - -struct SessionDetailsPageView: View { - @StateObject private var viewModel = SessionDetailViewModel() - - var body: some View { - GeometryReader { grr in - VStack { - ScrollView { - content - .padding(.horizontal, 16) - } - .frame(minHeight: grr.size.height-120) - - NavigationFooter(page: "Home") - } - .applySessionDetailsHeaderModifier() - .edgesIgnoringSafeArea(.bottom) - } - } - - var content: some View { - VStack { - Group { - HStack { - Text("Mentor") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.top, 28) - .padding(.bottom, 20) - - MentorCard(isEmpty: true) - .padding(.bottom, 28) - } - - Group { - HStack { - Text("Date & Time") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text("Monday, January 23, 2023") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text("9:00 - 10:00 AM PT") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - - Spacer() - } - .padding(.bottom, 10) - } - - Button { - - } label: { - Text("Reschedule Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(OutlinedButtonStyle()) - .padding(.bottom, 20) - - Group { - HStack { - Text("Location") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, 5) - - HStack { - Text("https://alum.zoom.us/my/timby") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("ALUM Dark Blue")) - - Spacer() - } - .padding(.bottom, 20) - } - - Group { - HStack { - Text("Pre-Session Form") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("NeutralGray4")) - - Spacer() - } - .padding(.bottom, viewModel.formIsComplete ? 20 : 5) - - if !viewModel.formIsComplete { - HStack { - FormIncompleteComponent(type: "Pre") - Spacer() - } - .padding(.bottom, 22) - } - - if !viewModel.formIsComplete { - Button { - - } label: { - Text("Complete Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } else { - Button { - - } label: { - Text("View Pre-Session Notes") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - } - .buttonStyle(FilledInButtonStyle()) - .padding(.bottom, 5) - } - } - - Button { - - } label: { - Text("Cancel Session") - .font(.custom("Metropolis-Regular", size: 17, relativeTo: .headline)) - .foregroundColor(Color("FunctionalError")) - } - .buttonStyle(OutlinedButtonStyle()) - .border(Color("FunctionalError")) - .cornerRadius(8.0) - } - } -} - -struct SessionDetailsPageView_Previews: PreviewProvider { - static var previews: some View { - SessionDetailsPageView() - } -} From d9ff7882424037c98414a05aabb6439aff5e8a38 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 28 May 2023 12:11:50 -0700 Subject: [PATCH 29/33] add a temporary logout button --- ALUM/ALUM/Views/HomeScreen.swift | 8 ++++++++ ALUM/ALUM/Views/MenteeProfileScreen.swift | 3 --- ALUM/ALUM/Views/MentorProfileScreen.swift | 3 --- ALUM/ALUM/Views/SessionDetailsScreen.swift | 8 ++++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ALUM/ALUM/Views/HomeScreen.swift b/ALUM/ALUM/Views/HomeScreen.swift index 8ada98f4..fe49eee1 100644 --- a/ALUM/ALUM/Views/HomeScreen.swift +++ b/ALUM/ALUM/Views/HomeScreen.swift @@ -27,6 +27,14 @@ struct HomeScreen: View { mentorView .customNavigationTitle("No Upcoming Session") } + Button { + FirebaseAuthenticationService.shared.logout() + } label: { + ALUMText(text: "Log out", textColor: ALUMColor.red) + } + .buttonStyle(OutlinedButtonStyle()) + .border(ALUMColor.red.color) + .cornerRadius(8.0) Spacer() } .padding(.horizontal, 16) diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index ac70020f..b0a606a0 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -54,9 +54,6 @@ struct MenteeProfileScreen: View { description if viewModel.selfView! { mentor - Button("Title", action: { - FirebaseAuthenticationService.shared.logout() - }) } } .frame(minHeight: grr.size.height - 50) diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index c0ed4c28..972e05c9 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -71,9 +71,6 @@ struct MentorProfileScreen: View { about if viewModel.selfView! { mentees - Button("Title", action: { - FirebaseAuthenticationService.shared.logout() - }) } } .frame(minHeight: grr.size.height - 50) diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index d0b2f869..c73a8a8d 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -84,6 +84,14 @@ struct SessionDetailsScreen: View { .border(ALUMColor.red.color) .cornerRadius(8.0) } + Button { + FirebaseAuthenticationService.shared.logout() + } label: { + ALUMText(text: "Log out", textColor: ALUMColor.red) + } + .buttonStyle(OutlinedButtonStyle()) + .border(ALUMColor.red.color) + .cornerRadius(8.0) Spacer() } .padding(.horizontal, 16) From 8bb2c1c0e2903ba8f31913c0f14c5c28f9b365fd Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 28 May 2023 14:26:10 -0700 Subject: [PATCH 30/33] create and hook the new logout button --- ALUM/ALUM.xcodeproj/project.pbxproj | 16 +++++++-- .../Logout Icon.imageset/Contents.json | 21 ++++++++++++ .../Logout Icon.imageset/log-out.png | Bin 0 -> 391 bytes .../FilledInButtonStyle.swift | 0 .../ButtonStyles/FullWidthButtonStyle.swift | 31 ++++++++++++++++++ .../OutlinedButtonStyle.swift | 0 ALUM/ALUM/Views/HomeScreen.swift | 8 ----- ALUM/ALUM/Views/MenteeProfileScreen.swift | 13 ++++++++ ALUM/ALUM/Views/MentorProfileScreen.swift | 13 ++++++++ ALUM/ALUM/Views/SessionDetailsScreen.swift | 8 ----- 10 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/Contents.json create mode 100644 ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/log-out.png rename ALUM/ALUM/Components/{ => ButtonStyles}/FilledInButtonStyle.swift (100%) create mode 100644 ALUM/ALUM/Components/ButtonStyles/FullWidthButtonStyle.swift rename ALUM/ALUM/Components/{ => ButtonStyles}/OutlinedButtonStyle.swift (100%) diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 14af2736..949ac1f2 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 0799ACE52A00924F00EEAFA2 /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */; }; 0799ACE92A00A6C200EEAFA2 /* APIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */; }; 0799ACF22A01109100EEAFA2 /* LoginReviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */; }; + 07A15DD52A23FB5F00C52798 /* FullWidthButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A15DD42A23FB5F00C52798 /* FullWidthButtonStyle.swift */; }; 07A565C82A1C3806008C96BC /* SessionDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */; }; 07CA49932A1C50CC00A81153 /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */; }; 07CA49962A1C53DA00A81153 /* ALUMColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07CA49942A1C53DA00A81153 /* ALUMColor.swift */; }; @@ -166,6 +167,7 @@ 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggedInRouter.swift; sourceTree = ""; }; 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConfig.swift; sourceTree = ""; }; 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginReviewPage.swift; sourceTree = ""; }; + 07A15DD42A23FB5F00C52798 /* FullWidthButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullWidthButtonStyle.swift; sourceTree = ""; }; 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailsScreen.swift; sourceTree = ""; }; 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = ""; }; 07CA49942A1C53DA00A81153 /* ALUMColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ALUMColor.swift; sourceTree = ""; }; @@ -343,6 +345,16 @@ name = "Recovered References"; sourceTree = ""; }; + 07A15DD32A23F69100C52798 /* ButtonStyles */ = { + isa = PBXGroup; + children = ( + 804AE4AC297B1DA4000B08F2 /* FilledInButtonStyle.swift */, + 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */, + 07A15DD42A23FB5F00C52798 /* FullWidthButtonStyle.swift */, + ); + path = ButtonStyles; + sourceTree = ""; + }; 07E885342A19F0B800B7AD27 /* Routers */ = { isa = PBXGroup; children = ( @@ -369,6 +381,7 @@ 07F9A50C297180D700BC11A8 /* Components */ = { isa = PBXGroup; children = ( + 07A15DD32A23F69100C52798 /* ButtonStyles */, 0793B4182A19FA7200AF78C8 /* CustomNavBar */, 0757551B29FAA2AB008E73FB /* Bullet.swift */, 0757551C29FAA2AB008E73FB /* CircleAddButton.swift */, @@ -376,12 +389,10 @@ 0757551929FAA2AB008E73FB /* DynamicProgressBarComponent.swift */, 0757551E29FAA2AB008E73FB /* MultipleChoiceList.swift */, 0757551A29FAA2AB008E73FB /* StaticProgressBarComponent.swift */, - 804AE4AC297B1DA4000B08F2 /* FilledInButtonStyle.swift */, 8095FE6F29E20C89006AA63C /* WhyPairedComponent.swift */, 8095FE6D29E1F968006AA63C /* NavigationFooter.swift */, 80210ECB29BA9EBC008B912A /* MentorCard.swift */, 80210EC029B95F24008B912A /* MenteeCard.swift */, - 2A8E07E1297B4FB0001AA153 /* OutlinedButtonStyle.swift */, 9752A4E92978B90F001E0AAB /* TextInputFieldComponent.swift */, 9756DF742982338300A0BCB5 /* InputValidationText.swift */, 9756DF76298233B500A0BCB5 /* InputValidationComponent.swift */, @@ -754,6 +765,7 @@ 0748208F29712921004AF547 /* ALUMApp.swift in Sources */, 80210EBD29B95D50008B912A /* ProfileModel.swift in Sources */, 97A2FE8C2989C20900405FD6 /* LoginScreen.swift in Sources */, + 07A15DD52A23FB5F00C52798 /* FullWidthButtonStyle.swift in Sources */, 07A565C82A1C3806008C96BC /* SessionDetailsScreen.swift in Sources */, 80210ED429C3DBD9008B912A /* FirebaseAuthenticationService.swift in Sources */, 0723BB862A1C995700911948 /* HomeScreen.swift in Sources */, diff --git a/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/Contents.json b/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/Contents.json new file mode 100644 index 00000000..19f4a658 --- /dev/null +++ b/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "log-out.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/log-out.png b/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/log-out.png new file mode 100644 index 0000000000000000000000000000000000000000..962446967ad7cc61eb3e780ef4632f6d18217c46 GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoM!3HGxw}u@9Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmFKsgyt7srqa#=BED@*Z;Fam`I&Fl!Lh2+VL| z_BzgCz-hp!JV93=z`=3FO@`FwFM<;B0e*rl7G2JhgTe~DWM4fh+mIC literal 0 HcmV?d00001 diff --git a/ALUM/ALUM/Components/FilledInButtonStyle.swift b/ALUM/ALUM/Components/ButtonStyles/FilledInButtonStyle.swift similarity index 100% rename from ALUM/ALUM/Components/FilledInButtonStyle.swift rename to ALUM/ALUM/Components/ButtonStyles/FilledInButtonStyle.swift diff --git a/ALUM/ALUM/Components/ButtonStyles/FullWidthButtonStyle.swift b/ALUM/ALUM/Components/ButtonStyles/FullWidthButtonStyle.swift new file mode 100644 index 00000000..37a1664b --- /dev/null +++ b/ALUM/ALUM/Components/ButtonStyles/FullWidthButtonStyle.swift @@ -0,0 +1,31 @@ +// +// FullWidthButtonStyle.swift +// ALUM +// +// Created by Aman Aggarwal on 5/28/23. +// + +import SwiftUI + +struct FullWidthButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(ALUMColor.white.color) + } +} + +struct FullWidthButtonStyle_Previews: PreviewProvider { + static var previews: some View { + Button(action: { + print("out") + }, label: { + HStack { + ALUMText(text: "Log out", textColor: ALUMColor.red) + Image("Logout Icon") + } + }) + .buttonStyle(FullWidthButtonStyle()) + } +} diff --git a/ALUM/ALUM/Components/OutlinedButtonStyle.swift b/ALUM/ALUM/Components/ButtonStyles/OutlinedButtonStyle.swift similarity index 100% rename from ALUM/ALUM/Components/OutlinedButtonStyle.swift rename to ALUM/ALUM/Components/ButtonStyles/OutlinedButtonStyle.swift diff --git a/ALUM/ALUM/Views/HomeScreen.swift b/ALUM/ALUM/Views/HomeScreen.swift index fe49eee1..8ada98f4 100644 --- a/ALUM/ALUM/Views/HomeScreen.swift +++ b/ALUM/ALUM/Views/HomeScreen.swift @@ -27,14 +27,6 @@ struct HomeScreen: View { mentorView .customNavigationTitle("No Upcoming Session") } - Button { - FirebaseAuthenticationService.shared.logout() - } label: { - ALUMText(text: "Log out", textColor: ALUMColor.red) - } - .buttonStyle(OutlinedButtonStyle()) - .border(ALUMColor.red.color) - .cornerRadius(8.0) Spacer() } .padding(.horizontal, 16) diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index b0a606a0..7fba41f7 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -54,6 +54,7 @@ struct MenteeProfileScreen: View { description if viewModel.selfView! { mentor + logOutButton } } .frame(minHeight: grr.size.height - 50) @@ -168,6 +169,18 @@ extension MenteeProfileScreen { } } } + + private var logOutButton: some View { + Button(action: { + FirebaseAuthenticationService.shared.logout() + }, label: { + HStack { + ALUMText(text: "Log out", textColor: ALUMColor.red) + Image("Logout Icon") + } + }) + .buttonStyle(FullWidthButtonStyle()) + } } struct MenteeProfileView_Previews: PreviewProvider { diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index 972e05c9..b5975524 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -71,6 +71,7 @@ struct MentorProfileScreen: View { about if viewModel.selfView! { mentees + logOutButton } } .frame(minHeight: grr.size.height - 50) @@ -197,6 +198,18 @@ extension MentorProfileScreen { .offset(y: -20) } } + + private var logOutButton: some View { + Button(action: { + FirebaseAuthenticationService.shared.logout() + }, label: { + HStack { + ALUMText(text: "Log out", textColor: ALUMColor.red) + Image("Logout Icon") + } + }) + .buttonStyle(FullWidthButtonStyle()) + } } struct MentorProfileScreen_Previews: PreviewProvider { diff --git a/ALUM/ALUM/Views/SessionDetailsScreen.swift b/ALUM/ALUM/Views/SessionDetailsScreen.swift index c73a8a8d..d0b2f869 100644 --- a/ALUM/ALUM/Views/SessionDetailsScreen.swift +++ b/ALUM/ALUM/Views/SessionDetailsScreen.swift @@ -84,14 +84,6 @@ struct SessionDetailsScreen: View { .border(ALUMColor.red.color) .cornerRadius(8.0) } - Button { - FirebaseAuthenticationService.shared.logout() - } label: { - ALUMText(text: "Log out", textColor: ALUMColor.red) - } - .buttonStyle(OutlinedButtonStyle()) - .border(ALUMColor.red.color) - .cornerRadius(8.0) Spacer() } .padding(.horizontal, 16) From f598799d5fe41ef61110c7d4e1e4f603b075e189 Mon Sep 17 00:00:00 2001 From: Aman Aggarwal Date: Sun, 28 May 2023 14:47:32 -0700 Subject: [PATCH 31/33] fix swiftlint --- ALUM/ALUM/Views/MenteeProfileScreen.swift | 2 +- ALUM/ALUM/Views/MentorProfileScreen.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ALUM/ALUM/Views/MenteeProfileScreen.swift b/ALUM/ALUM/Views/MenteeProfileScreen.swift index 7fba41f7..6a94381c 100644 --- a/ALUM/ALUM/Views/MenteeProfileScreen.swift +++ b/ALUM/ALUM/Views/MenteeProfileScreen.swift @@ -169,7 +169,7 @@ extension MenteeProfileScreen { } } } - + private var logOutButton: some View { Button(action: { FirebaseAuthenticationService.shared.logout() diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index b5975524..e86a76a8 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -198,7 +198,7 @@ extension MentorProfileScreen { .offset(y: -20) } } - + private var logOutButton: some View { Button(action: { FirebaseAuthenticationService.shared.logout() From 8da98a7986e859419e179fec7f26c06e0110c4f3 Mon Sep 17 00:00:00 2001 From: Philip Zhang Date: Sun, 28 May 2023 19:24:08 -0700 Subject: [PATCH 32/33] implement network/internal error popup --- .../NoConnectionIcon.imageset/Contents.json | 21 +++++ .../no-connection.svg | 6 ++ ALUM/ALUM/Views/Routers/RootRouter.swift | 78 +++++++++++++++---- 3 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/Contents.json create mode 100644 ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/no-connection.svg diff --git a/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/Contents.json b/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/Contents.json new file mode 100644 index 00000000..41260106 --- /dev/null +++ b/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "no-connection.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/no-connection.svg b/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/no-connection.svg new file mode 100644 index 00000000..50f55294 --- /dev/null +++ b/ALUM/ALUM/Assets.xcassets/NoConnectionIcon.imageset/no-connection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ALUM/ALUM/Views/Routers/RootRouter.swift b/ALUM/ALUM/Views/Routers/RootRouter.swift index f4ac13b3..66aa0a67 100644 --- a/ALUM/ALUM/Views/Routers/RootRouter.swift +++ b/ALUM/ALUM/Views/Routers/RootRouter.swift @@ -11,23 +11,72 @@ struct RootRouter: View { @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared var body: some View { - if self.currentUser.isLoading { - LoadingView(text: "RootView") - .onAppear(perform: { - Task { - await self.currentUser.setForInSessionUser() + ZStack { + if self.currentUser.isLoading { + LoadingView(text: "RootView") + .onAppear(perform: { + Task { + await self.currentUser.setForInSessionUser() + } + }) + } else if self.currentUser.isLoggedIn == false { + NavigationView { + LoginScreen() } - }) - } else if self.currentUser.isLoggedIn == false { - NavigationView { - LoginScreen() + } else if self.currentUser.status == "paired" { + CustomNavView { + LoggedInRouter(defaultSelection: 0) + } + } else { + UnpairedScreen() } - } else if self.currentUser.status == "paired" { - CustomNavView { - LoggedInRouter(defaultSelection: 0) + errorMessage + } + } + + var errorMessage: some View { + Group { + if self.currentUser.showInternalError || self.currentUser.showNetworkError { + GeometryReader { geometry in + VStack { + Spacer() + VStack(spacing: 12) { + if self.currentUser.showNetworkError { + Image("NoConnectionIcon") + Text("No internet connection") + .font(Font.custom("Metropolis-Regular", size: 17)) + Text("Please check your connection and try again.") + .font(.custom("Metropolis-Regular", size: 13)) + .foregroundColor(Color("NeutralGray2")) + } + + if self.currentUser.showInternalError { + Image(systemName: "xmark.circle") + .font(.system(size: 50)) + .foregroundColor(Color("ALUM Alert Red")) + Text("Something went wrong") + .font(Font.custom("Metropolis-Regular", size: 17)) + Text("Please contact the ALUM team.") + .font(.custom("Metropolis-Regular", size: 13)) + .foregroundColor(Color("NeutralGray2")) + } + + Button("Dismiss") { + self.currentUser.showInternalError = false + self.currentUser.showNetworkError = false + } + .frame(minWidth: 50, maxWidth: 100) + .frame(minHeight: 0, maxHeight: 48) + .padding(.top, 20) + .buttonStyle(FilledInButtonStyle(disabled: false)) + } + .frame(width: geometry.size.width) + .padding(.vertical, 36) + .background(Rectangle().fill(Color.white).shadow(radius: 8)) + } + .edgesIgnoringSafeArea(.bottom) + } } - } else { - UnpairedScreen() } } } @@ -40,6 +89,7 @@ struct RootView_Previews: PreviewProvider { uid: "6431b9a2bcf4420fe9825fe5", role: .mentor ) + CurrentUserModel.shared.showInternalError = true return RootRouter().onAppear(perform: { Task { try await CurrentUserModel.shared.fetchUserInfoFromServer( From e0520f5e463b870f138bf6ce18feffd8384e8106 Mon Sep 17 00:00:00 2001 From: Philip Zhang Date: Sun, 28 May 2023 19:24:38 -0700 Subject: [PATCH 33/33] show the error popup --- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../xcshareddata/xcschemes/ALUM.xcscheme | 77 +++++++++++++++++++ ALUM/ALUM/CustomErrors.swift | 3 + ALUM/ALUM/Models/CurrentUserModel.swift | 16 ++++ .../FirebaseAuthenticationService.swift | 6 ++ ALUM/ALUM/Services/NotesService.swift | 6 ++ ALUM/ALUM/Services/ServiceHelper.swift | 12 +++ ALUM/ALUM/Services/SessionService.swift | 6 ++ ALUM/ALUM/Services/UserService.swift | 6 ++ .../CalendlyBooking/CalendlyBooking.swift | 6 ++ 10 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme diff --git a/ALUM/ALUM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ALUM/ALUM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1f4ecfbb..5f07747b 100644 --- a/ALUM/ALUM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ALUM/ALUM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { - "revision" : "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", - "version" : "2.2.0" + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" } }, { diff --git a/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme b/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme new file mode 100644 index 00000000..e55220dd --- /dev/null +++ b/ALUM/ALUM.xcodeproj/xcshareddata/xcschemes/ALUM.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ALUM/ALUM/CustomErrors.swift b/ALUM/ALUM/CustomErrors.swift index 635f9b35..9bea7e3e 100644 --- a/ALUM/ALUM/CustomErrors.swift +++ b/ALUM/ALUM/CustomErrors.swift @@ -42,5 +42,8 @@ func handleDecodingErrors(_ decodingClosure: () throws -> T) throws -> T { errorMessage = "Unknown: \(error)" } + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.invalidResponse, message: "Decode error - \(errorMessage)") } diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index 316cc6c5..f623d1c2 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -22,6 +22,8 @@ class CurrentUserModel: ObservableObject { @Published var isLoggedIn: Bool @Published var status: String? @Published var showTabBar: Bool + @Published var showInternalError: Bool + @Published var showNetworkError: Bool @Published var sessionId: String? @Published var pairedMentorId: String? @@ -34,6 +36,8 @@ class CurrentUserModel: ObservableObject { self.role = nil self.status = nil self.showTabBar = true + self.showInternalError = false + self.showNetworkError = false } /// Since async operations are involved, this function will limit updating the current @@ -52,6 +56,9 @@ class CurrentUserModel: ObservableObject { func setForInSessionUser() async { do { guard let user = Auth.auth().currentUser else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.actionable(.authenticationError, message: "No user found") } try await self.setFromFirebaseUser(user: user) @@ -65,6 +72,9 @@ class CurrentUserModel: ObservableObject { func setFromFirebaseUser(user: User) async throws { let result = try await user.getIDTokenResult() guard let role = result.claims["role"] as? String else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.actionable( .authenticationError, message: "Expected to have a firebase role for user \(user.uid)" @@ -105,6 +115,9 @@ class CurrentUserModel: ObservableObject { if self.role == .mentee { guard let userPairedMentorId = userData.pairedMentorId else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.invalidResponse, message: "Expected mentee to have a paired mentor Id") } print("userPairedMentorId - \(userPairedMentorId)") @@ -113,6 +126,9 @@ class CurrentUserModel: ObservableObject { } } else if self.role == .mentor { guard let userPairedMenteeId = userData.pairedMenteeId else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.invalidResponse, message: "Expected mentor to have a paired mentee Id") } print("userPairedMenteeId - \(userPairedMenteeId)") diff --git a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift index 533ff89d..23fa2f5e 100644 --- a/ALUM/ALUM/Services/FirebaseAuthenticationService.swift +++ b/ALUM/ALUM/Services/FirebaseAuthenticationService.swift @@ -35,12 +35,18 @@ final class FirebaseAuthenticationService: ObservableObject { return tokenResult.token } catch let error { // Handle the error + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.actionable( .authenticationError, message: "Error getting auth token: \(error.localizedDescription)" ) } } else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.actionable(.authenticationError, message: "No logged in user found. Please login first") } } diff --git a/ALUM/ALUM/Services/NotesService.swift b/ALUM/ALUM/Services/NotesService.swift index c4771827..2a8c0958 100644 --- a/ALUM/ALUM/Services/NotesService.swift +++ b/ALUM/ALUM/Services/NotesService.swift @@ -64,6 +64,9 @@ class NotesService { let route = APIRoute.patchNote(noteId: noteId) var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(data) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") } request.httpBody = jsonData @@ -82,6 +85,9 @@ class NotesService { return notesData } catch { print("Failed to decode data") + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Decode Data") } } diff --git a/ALUM/ALUM/Services/ServiceHelper.swift b/ALUM/ALUM/Services/ServiceHelper.swift index 6642116c..05140ec4 100644 --- a/ALUM/ALUM/Services/ServiceHelper.swift +++ b/ALUM/ALUM/Services/ServiceHelper.swift @@ -12,6 +12,9 @@ class ServiceHelper { func attachAuthTokenToRequest(request: inout URLRequest) async throws { guard let authToken = try await FirebaseAuthenticationService.shared.getCurrentAuth() else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.actionable(.authenticationError, message: "Error getting auth token") } request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") @@ -19,6 +22,9 @@ class ServiceHelper { func createRequest(urlString: String, method: String, requireAuth: Bool) async throws -> URLRequest { guard let url = URL(string: urlString) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.unknownError, message: "Invalid URL") } @@ -43,11 +49,17 @@ class ServiceHelper { do { (responseData, response) = try await URLSession.shared.data(for: request) } catch { + DispatchQueue.main.async { + CurrentUserModel.shared.showNetworkError = true + } throw AppError.actionable(.networkError, message: route.label) } // Ensure that response is of corrcet type guard let httpResponse = response as? HTTPURLResponse else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError( .invalidResponse, message: "Expected HTTPURLResponse for getMentor route but found somrthing else" diff --git a/ALUM/ALUM/Services/SessionService.swift b/ALUM/ALUM/Services/SessionService.swift index ae457d67..d8f9e296 100644 --- a/ALUM/ALUM/Services/SessionService.swift +++ b/ALUM/ALUM/Services/SessionService.swift @@ -105,12 +105,18 @@ class SessionService { var request = try await route.createURLRequest() let sessionBodyData = SessionLink(calendlyURI: calendlyURI) guard let jsonData = try? JSONEncoder().encode(sessionBodyData) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.invalidRequest, message: "Error encoding JSON Data") } request.httpBody = jsonData let responseData = try await ServiceHelper.shared.sendRequestWithSafety(route: route, request: request) guard let sessionData = try? JSONDecoder().decode(PostSessionData.self, from: responseData) else { print("Failed to decode data") + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.invalidRequest, message: "Could not decode data") } print("SUCCESS - \(route.label)") diff --git a/ALUM/ALUM/Services/UserService.swift b/ALUM/ALUM/Services/UserService.swift index 414076b0..64bd1427 100644 --- a/ALUM/ALUM/Services/UserService.swift +++ b/ALUM/ALUM/Services/UserService.swift @@ -72,6 +72,9 @@ class UserService { let route = APIRoute.postMentee var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(data) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") } request.httpBody = jsonData @@ -83,6 +86,9 @@ class UserService { let route = APIRoute.postMentor var request = try await route.createURLRequest() guard let jsonData = try? JSONEncoder().encode(data) else { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") } request.httpBody = jsonData diff --git a/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift b/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift index 520a5057..1054d1da 100644 --- a/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift +++ b/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift @@ -28,6 +28,9 @@ struct CalendlyView: UIViewRepresentable { let request = try await route.createURLRequest() uiView.load(request) } catch { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.unknownError, message: "Error Loading Calendly Link") } } @@ -58,6 +61,9 @@ struct CalendlyView: UIViewRepresentable { CurrentUserModel.shared.isLoading = true } } catch { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.unknownError, message: "Error posting a new session") } }