diff --git a/ALUM/ALUM.xcodeproj/project.pbxproj b/ALUM/ALUM.xcodeproj/project.pbxproj index 7ad775a7..8a6b8108 100644 --- a/ALUM/ALUM.xcodeproj/project.pbxproj +++ b/ALUM/ALUM.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* 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 */; }; @@ -21,20 +26,30 @@ 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 */; }; + 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 */; }; 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 */; }; + 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 */; }; @@ -93,8 +108,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 */; }; @@ -118,11 +131,15 @@ 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 */ /* 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 = ""; }; @@ -138,19 +155,29 @@ 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 = ""; }; + 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 = ""; }; 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 = ""; }; + 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 = ""; }; @@ -191,8 +218,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 = ""; }; @@ -233,7 +258,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 */ @@ -310,6 +334,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 = ( @@ -317,21 +353,54 @@ 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 = ( + 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */, + 07E885352A19F0D300B7AD27 /* RootRouter.swift */, + 0723BB922A1D576800911948 /* ProfileTabRouter.swift */, + 0723BB942A1D578400911948 /* HomeTabRouter.swift */, + ); + path = Routers; + sourceTree = ""; + }; + 07ECF5F82A22A94A0093C37B /* PostSessionForm */ = { + isa = PBXGroup; + children = ( + 974D62D029FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift */, + 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */, + 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */, + 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */, + 07ECF5F62A2279940093C37B /* PostSessionFormRouter.swift */, + ); + path = PostSessionForm; + sourceTree = ""; + }; 07F9A50C297180D700BC11A8 /* Components */ = { isa = PBXGroup; children = ( + 07A15DD32A23F69100C52798 /* ButtonStyles */, + 0793B4182A19FA7200AF78C8 /* CustomNavBar */, 0757551B29FAA2AB008E73FB /* Bullet.swift */, 0757551C29FAA2AB008E73FB /* CircleAddButton.swift */, 0757551D29FAA2AB008E73FB /* CustomAlertView.swift */, 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 */, @@ -374,7 +443,9 @@ isa = PBXGroup; children = ( 2C27FDC62A18544E007F66A9 /* EditProfile */, - 979303D029E7EA680053C30E /* SessionDetailsView */, + 07A565C72A1C3806008C96BC /* SessionDetailsScreen.swift */, + 07ECF5F82A22A94A0093C37B /* PostSessionForm */, + 07E885342A19F0B800B7AD27 /* Routers */, 0799ACF12A01109000EEAFA2 /* LoginReviewPage.swift */, 97A31AC729BAFA7600B79D30 /* PreSessionForm */, 97A2FE8B2989C20900405FD6 /* LoginScreen.swift */, @@ -384,7 +455,9 @@ 97992B5D29A6E7E200701CC7 /* SignUpPage */, 0748209029712921004AF547 /* ContentView.swift */, 0799ACE22A007E8A00EEAFA2 /* LoadingScreen.swift */, - 0799ACE42A00924F00EEAFA2 /* LoggedInRouter.swift */, + 0723BB852A1C995700911948 /* HomeScreen.swift */, + 0723BB8E2A1CDF7500911948 /* UnpairedScreen.swift */, + 0757552C29FAA2D6008E73FB /* ConfirmationScreen.swift */, ); path = Views; sourceTree = ""; @@ -411,7 +484,6 @@ 979303DF29E8B06E0053C30E /* SessionService.swift */, 8045EEB029F6844A00BD179C /* ServiceHelper.swift */, 0799ACE82A00A6C200EEAFA2 /* APIConfig.swift */, - E365F71D29FA0D110055A2F4 /* SessionService.swift */, ); path = Services; sourceTree = ""; @@ -425,17 +497,6 @@ path = EditProfile; sourceTree = ""; }; - 979303D029E7EA680053C30E /* SessionDetailsView */ = { - isa = PBXGroup; - children = ( - 979303D929E882E20053C30E /* MentorSessionDetailsPage.swift */, - 979303DB29E885140053C30E /* MenteeSessionsDetailsPage.swift */, - 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, - 974D62D029FAFA7C0096FE80 /* ViewPostSessionNotesPage.swift */, - ); - path = SessionDetailsView; - sourceTree = ""; - }; 97992B5D29A6E7E200701CC7 /* SignUpPage */ = { isa = PBXGroup; children = ( @@ -453,14 +514,10 @@ 97A31AC729BAFA7600B79D30 /* PreSessionForm */ = { isa = PBXGroup; children = ( - 0757552B29FAA2D6008E73FB /* MissedSessionScreen.swift */, - 0757552629FAA2D5008E73FB /* PostSessionConfirmationScreen.swift */, - 0757552529FAA2D5008E73FB /* PostSessionQuestionScreen.swift */, - 0757552929FAA2D5008E73FB /* PostSessionView.swift */, + 974D62CE29FAF0A00096FE80 /* ViewPreSessionNotesPage.swift */, 0757552829FAA2D5008E73FB /* PreSessionConfirmationScreen.swift */, 0757552A29FAA2D6008E73FB /* PreSessionQuestionScreen.swift */, - 0757552729FAA2D5008E73FB /* PreSessionView.swift */, - 0757552C29FAA2D6008E73FB /* SessionConfirmationScreen.swift */, + 0723BB902A1D4ADE00911948 /* PreSessionFormRouter.swift */, ); path = PreSessionForm; sourceTree = ""; @@ -494,8 +551,11 @@ 97EB2BAD29B59CF5001988D9 /* Constants */ = { isa = PBXGroup; children = ( + 07CA49942A1C53DA00A81153 /* ALUMColor.swift */, + 07CA49952A1C53DA00A81153 /* ALUMText.swift */, 0726C9ED29DC4AE20042A486 /* UserApplicationConstants */, 97EB2BAE29B59D08001988D9 /* ErrorFunctions */, + 07CA49922A1C50CC00A81153 /* DevelopmentModels.swift */, ); path = Constants; sourceTree = ""; @@ -638,9 +698,12 @@ buildActionMask = 2147483647; files = ( 0757553C29FAA387008E73FB /* NotesService.swift in Sources */, + 0723BB912A1D4ADE00911948 /* PreSessionFormRouter.swift in Sources */, 0794653429DC536200B68EFD /* InputValidationComponent.swift in Sources */, 2C49296A2A1AD39A0060C0B7 /* EditMentorProfile.swift in Sources */, 8095FE7229E41214006AA63C /* NavigationComponent.swift in Sources */, + 07CA49932A1C50CC00A81153 /* DevelopmentModels.swift in Sources */, + 07CA49962A1C53DA00A81153 /* ALUMColor.swift in Sources */, 0757552029FAA2AB008E73FB /* StaticProgressBarComponent.swift in Sources */, 2C49296E2A1ADAE90060C0B7 /* AboutInput.swift in Sources */, 8071E52D2A044E2900BD11A3 /* CurrentUserModel.swift in Sources */, @@ -656,26 +719,25 @@ 80210ECA29BA82E7008B912A /* MenteeProfileViewModel.swift in Sources */, 8041161E29B21BB800529936 /* MentorProfileScreen.swift in Sources */, 8095FE7029E20C89006AA63C /* WhyPairedComponent.swift in Sources */, - E365F71E29FA0D110055A2F4 /* SessionService.swift in Sources */, + 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 */, 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 */, 2C27FDC82A1854CF007F66A9 /* EditMenteeProfile.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 */, @@ -689,24 +751,29 @@ 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 */, 2C49296C2A1AD88E0060C0B7 /* EditProfileImage.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 */, 97F6CDF629BD4E3D00DFBB99 /* CareerInterests.swift in Sources */, 741D534229A4EDD3004C04E6 /* TagDisplay.swift in Sources */, 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 */, @@ -714,14 +781,19 @@ 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 */, 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 */, + 07A15DD52A23FB5F00C52798 /* FullWidthButtonStyle.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/ALUMApp.swift b/ALUM/ALUM/ALUMApp.swift index 2988a427..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 { - MenteeSessionsDetailsPage() - } - } -} - @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/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/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 00000000..c06314f0 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120) 1.png differ 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 00000000..c06314f0 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (120x120).png differ 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 00000000..6168e995 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (180x180).png differ 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 00000000..f7e4c953 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (40x40).png differ 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 00000000..1828b0d0 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (58x58).png differ 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 00000000..62157719 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (60x60).png differ 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 00000000..4f3138a0 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (80x80).png differ diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (87x87).png b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (87x87).png new file mode 100644 index 00000000..5dff7e14 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/ALUM app icon (87x87).png differ diff --git a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json index 171ab03d..da2b46f1 100644 --- a/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ALUM/ALUM/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,9 +1,101 @@ { "images" : [ { - "filename" : "ALUM app icon.png", - "idiom" : "universal", - "platform" : "ios", + "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" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", "size" : "1024x1024" } ], 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 00000000..96244696 Binary files /dev/null and b/ALUM/ALUM/Assets.xcassets/Logout Icon.imageset/log-out.png differ 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/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/Components/CustomNavBar/CustomNavBarContainerView.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift new file mode 100644 index 00000000..4d3721c2 --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarContainerView.swift @@ -0,0 +1,63 @@ +// +// CustomNavBarContainerView.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +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 = 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 { + 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(CustomNavBarBackHiddenPreferenceKey.self, perform: { value in + self.showBackButton = !value + }) + .onPreferenceChange(CustomNavBarIsHiddenPreferenceKey.self, perform: { value in + self.isHidden = value + }) + } +} + +struct CustomNavBarContainerView_Previews: PreviewProvider { + static var previews: some View { + CustomNavBarContainerView { + VStack(spacing: 0) { + LoginReviewPage(text: ["Hello", "Hi"]) + } + } + } +} diff --git a/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift new file mode 100644 index 00000000..d5510e0a --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavBarPreferenceKeys.swift @@ -0,0 +1,66 @@ +// +// CustomNavBarPreferenceKeys.swift +// ALUM +// +// Created by Yash Ravipati on 5/18/23. +// + +import Foundation +import SwiftUI + +struct CustomNavBarTitlePreferenceKey: PreferenceKey { + static var defaultValue: String = CustomNavBarDefaultValues.title + + static func reduce(value: inout String, nextValue: () -> String) { + value = nextValue() + } +} + +struct CustomNavBarIsHiddenPreferenceKey: PreferenceKey { + static var defaultValue: Bool = CustomNavBarDefaultValues.barIsHidden + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + +struct CustomNavBarIsPurplePreferenceKey: PreferenceKey { + static var defaultValue: Bool = CustomNavBarDefaultValues.barIsPurple + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + +struct CustomNavBarBackHiddenPreferenceKey: PreferenceKey { + static var defaultValue: Bool = !CustomNavBarDefaultValues.showBackButton + + static func reduce(value: inout Bool, nextValue: () -> Bool) { + value = nextValue() + } +} + +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: CustomNavBarBackHiddenPreferenceKey.self, value: hidden) + } + + func customNavBarItems(title: String, isPurple: Bool, backButtonHidden: Bool) -> 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..0d895c2b --- /dev/null +++ b/ALUM/ALUM/Components/CustomNavBar/CustomNavLink.swift @@ -0,0 +1,41 @@ +// +// 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..875762c9 --- /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 { + MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") + .customNavigationTitle("Title 2") + .customNavigationBarBackButtonHidden(false) + } + } +} diff --git a/ALUM/ALUM/Components/HorizontalMenteeCard.swift b/ALUM/ALUM/Components/HorizontalMenteeCard.swift index ac35b071..7ada0c6b 100644 --- a/ALUM/ALUM/Components/HorizontalMenteeCard.swift +++ b/ALUM/ALUM/Components/HorizontalMenteeCard.swift @@ -8,54 +8,75 @@ 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 { - } label: { - ZStack { - RoundedRectangle(cornerRadius: 12.0) - .frame(width: 358, height: 118) - .foregroundColor(Color("ALUM Dark Blue")) - if isEmpty { - Circle() - .frame(width: 85) - .foregroundColor(Color("NeutralGray1")) - .offset(x: -112.5) - } else { - profilePic - .resizable() - .clipShape(Circle()) - .frame(width: 85, height: 85) - .offset(x: -112.5) + 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 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(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(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) } } } @@ -63,6 +84,10 @@ struct HorizontalMenteeCard: View { struct HorizontalMenteeCard_Previews: PreviewProvider { static var previews: some View { - HorizontalMenteeCard() + 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/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/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..d99f5f40 --- /dev/null +++ b/ALUM/ALUM/Constants/ALUMText.swift @@ -0,0 +1,47 @@ +// +// 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..69c9723a --- /dev/null +++ b/ALUM/ALUM/Constants/DevelopmentModels.swift @@ -0,0 +1,105 @@ +// +// 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", + 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: [], + 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?", + 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/CustomErrors.swift b/ALUM/ALUM/CustomErrors.swift index 644c4001..9bea7e3e 100644 --- a/ALUM/ALUM/CustomErrors.swift +++ b/ALUM/ALUM/CustomErrors.swift @@ -25,3 +25,25 @@ 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)" + } + + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } + throw AppError.internalError(.invalidResponse, message: "Decode error - \(errorMessage)") +} diff --git a/ALUM/ALUM/Info.plist b/ALUM/ALUM/Info.plist index 5d1b7b9e..2d8b38ed 100644 --- a/ALUM/ALUM/Info.plist +++ b/ALUM/ALUM/Info.plist @@ -11,5 +11,7 @@ Metropolis-ExtraBoldItalic.otf Metropolis-Thin.otf + UIUserInterfaceStyle + Light diff --git a/ALUM/ALUM/Models/CurrentUserModel.swift b/ALUM/ALUM/Models/CurrentUserModel.swift index a5ed9ffc..f623d1c2 100644 --- a/ALUM/ALUM/Models/CurrentUserModel.swift +++ b/ALUM/ALUM/Models/CurrentUserModel.swift @@ -20,12 +20,24 @@ class CurrentUserModel: ObservableObject { @Published var uid: String? @Published var role: UserRole? @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? + @Published var pairedMenteeId: String? init() { self.isLoading = true self.isLoggedIn = false self.uid = nil 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 @@ -44,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) @@ -57,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)" @@ -74,6 +92,64 @@ 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) + 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 { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } + 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 { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } + 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.sessionId = userData.sessionId + } + } + + 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/Models/SessionModel.swift b/ALUM/ALUM/Models/SessionModel.swift index 7b7e8638..fb3b261a 100644 --- a/ALUM/ALUM/Models/SessionModel.swift +++ b/ALUM/ALUM/Models/SessionModel.swift @@ -21,3 +21,23 @@ 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 + var missedSessionReason: String? +} diff --git a/ALUM/ALUM/Services/APIConfig.swift b/ALUM/ALUM/Services/APIConfig.swift index 488fda8c..41fed8f9 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,16 @@ 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 +102,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 +114,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 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 c9adc5e1..2a8c0958 100644 --- a/ALUM/ALUM/Services/NotesService.swift +++ b/ALUM/ALUM/Services/NotesService.swift @@ -61,10 +61,12 @@ 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 { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.jsonParsingError, message: "Failed to Encode Data") } request.httpBody = jsonData @@ -83,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 dfd8dd6e..d8f9e296 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: @@ -110,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 2e9cc6f5..64bd1427 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 sessionId: String? + var pairedMentorId: String? + var pairedMenteeId: String? +} + struct MenteeGetData: Decodable { var message: String var mentee: MenteeInfo @@ -48,10 +55,26 @@ 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() 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 @@ -63,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 @@ -74,9 +100,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 +111,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/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift b/ALUM/ALUM/ViewModels/MenteeProfileViewModel.swift index 5dec2930..13841e0a 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/QuestionViewModel.swift b/ALUM/ALUM/ViewModels/QuestionViewModel.swift index 6d61a27b..a0aa9c71 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 submitMissedNotesPatch(noteID: String) async throws { var notesData: [QuestionPatchData] = [] notesData.append(QuestionPatchData(answer: PatchAnswer.string(missedOption), @@ -94,32 +41,40 @@ 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 -> [Question] { + 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) } - self.isLoading = false + return newQuestions } - 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 fetchPreSessionNotes(noteId: 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 questions = try await self.fetchNotes(noteId: noteId) + DispatchQueue.main.async { + self.isLoading = false + self.questionList = questions + } + } + + func fetchPostSessionNotes(notesId: String, otherNotesId: String) async throws { + DispatchQueue.main.async { + self.isLoading = true + } + 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 } func nextQuestion() { diff --git a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift index e6739cc3..12a9509a 100644 --- a/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift +++ b/ALUM/ALUM/ViewModels/SessionDetailViewModel.swift @@ -11,90 +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", - personalAccessToken: "", - 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/CalendlyBooking/CalendlyBooking.swift b/ALUM/ALUM/Views/CalendlyBooking/CalendlyBooking.swift index 88617c67..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") } } @@ -53,8 +56,14 @@ 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 { + DispatchQueue.main.async { + CurrentUserModel.shared.showInternalError.toggle() + } throw AppError.internalError(.unknownError, message: "Error posting a new session") } } diff --git a/ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift b/ALUM/ALUM/Views/ConfirmationScreen.swift similarity index 92% rename from ALUM/ALUM/Views/PreSessionForm/SessionConfirmationScreen.swift rename to ALUM/ALUM/Views/ConfirmationScreen.swift index bae9d6db..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 @@ -39,8 +39,8 @@ struct SessionConfirmationScreen: View { .padding(16) Spacer() - NavigationLink( - destination: MentorSessionDetailsPage().navigationBarHidden(true), + CustomNavLink( + destination: LoggedInRouter(defaultSelection: 0), label: { HStack { Text(text[2]) @@ -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/HomeScreen.swift b/ALUM/ALUM/Views/HomeScreen.swift new file mode 100644 index 00000000..8ada98f4 --- /dev/null +++ b/ALUM/ALUM/Views/HomeScreen.swift @@ -0,0 +1,104 @@ +// +// 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/LoadingScreen.swift b/ALUM/ALUM/Views/LoadingScreen.swift index 3a4cb19b..dedaa113 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 { - Text(text) + Spacer() +// Text(text) ProgressView() + Spacer() } } } diff --git a/ALUM/ALUM/Views/LoggedInRouter.swift b/ALUM/ALUM/Views/LoggedInRouter.swift deleted file mode 100644 index 50214fac..00000000 --- a/ALUM/ALUM/Views/LoggedInRouter.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// CustomTabView.swift -// ALUM -// -// Created by Aman Aggarwal on 5/1/23. -// - -import SwiftUI - -struct ProfileRouter: 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 PlaceHolderHomeScreen: View { - var body: some View { - NavigationView { - VStack { - Text("PlaceHolderHomeScreen") - Button("Sign Out", action: { - FirebaseAuthenticationService.shared.logout() - }) - } - } - } -} - -struct LoggedInRouter: View { - // (todo) Needs to be customized to match our design - @State private var selection = 0 - init() { - UITabBar.appearance().backgroundColor = UIColor(Color.white) // custom color. - } - - let tabItems = [ - TabBarItem(iconName: "ALUM Home", title: "Home"), - TabBarItem(iconName: "GrayCircle", title: "Profile") - ] - - var body: some View { - VStack(spacing: 0) { - switch selection { - case 0: - MentorSessionDetailsPage() - case 1: - ProfileRouter() - default: - Text("Error") - } - ZStack(alignment: .bottom) { - HStack(spacing: 0) { - ForEach(0..= -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)) - - // temporary, move to header - NavigationLink(destination: - EditMenteeProfileScreen(uID: uID) - ) { - Text("Edit").padding() - } - } - 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 ?? "")) { - MentorCard(isEmpty: true, uID: mentee.mentorId ?? "") - .padding(.bottom, 10) - } + mentor + logOutButton } } .frame(minHeight: grr.size.height - 50) @@ -122,43 +63,144 @@ struct MenteeProfileScreen: View { .edgesIgnoringSafeArea(.bottom) } ZStack { - if !viewModel.selfView! { - // params currently placeholders for later navigation - if scrollAtTop { - NavigationHeaderComponent( - backText: "Login", - backDestination: LoginScreen(), - title: "Mentee Profile", - purple: true, - showButton: false - ) - .background(Color("ALUM Primary Purple")) - } else { - NavigationHeaderComponent( - backText: "Login", - backDestination: LoginScreen(), - title: "Mentee Profile", - purple: false, - showButton: false - ) - .background(.white) - } + // 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 { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) - .background(Color("ALUM Primary Purple")) - } else { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) - .background(.white) - } + 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) + } + } + } + + 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 { static var previews: some View { - MenteeProfileScreen(uID: "6431b99ebcf4420fe9825fe3") + 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" + ) + } + } + } } } diff --git a/ALUM/ALUM/Views/MentorProfileScreen.swift b/ALUM/ALUM/Views/MentorProfileScreen.swift index b9caf9a2..e86a76a8 100644 --- a/ALUM/ALUM/Views/MentorProfileScreen.swift +++ b/ALUM/ALUM/Views/MentorProfileScreen.swift @@ -13,6 +13,8 @@ struct MentorProfileScreen: View { @State var scrollAtTop: Bool = true @State var uID: String = "" @State public var showWebView = false + @State var prevView: AnyView = AnyView(LoadingView(text: "")) + @ObservedObject var currentUser: CurrentUserModel = CurrentUserModel.shared var body: some View { Group { @@ -20,8 +22,11 @@ struct MentorProfileScreen: View { LoadingView(text: "MentorProfileScreen") } else { content + .customNavigationIsPurple(scrollAtTop) + .padding(.top, 0) } - }.onAppear(perform: { + } + .onAppear(perform: { Task { do { try await viewModel.fetchMentorInfo(userID: uID) @@ -41,60 +46,15 @@ struct MentorProfileScreen: View { 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)) - - // temporary, move to header - NavigationLink(destination: - EditMentorProfileScreen(uID: uID) - ) { - Text("Edit").padding() - } - } - - 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") @@ -108,42 +68,10 @@ 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]) - ) { - MenteeCard(isEmpty: true, uID: mentor.menteeIds![index]) - .padding(.bottom, 15) - .padding(.trailing, 10) - } - } - .padding(.bottom, 30) - .padding(.leading, 16) - .offset(y: -20) - } + mentees + logOutButton } } .frame(minHeight: grr.size.height - 50) @@ -152,41 +80,156 @@ struct MentorProfileScreen: View { .edgesIgnoringSafeArea(.bottom) } ZStack { - if !viewModel.selfView! { - // params currently placeholders for later navigation - if scrollAtTop { - NavigationHeaderComponent( - backText: "Login", - backDestination: LoginScreen(), - title: "Mentor Profile", - purple: true, - showButton: false) - .background(Color("ALUM Primary Purple")) - } else { - NavigationHeaderComponent( - backText: "Login", - backDestination: LoginScreen(), - title: "Mentor Profile", - purple: false, - showButton: false) - .background(.white) - } + // 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 { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: true) - .background(Color("ALUM Primary Purple")) - } else { - ProfileHeaderComponent(profile: true, title: "My Profile", purple: false) - .background(.white) - } + if scrollAtTop { + Rectangle() + .frame(height: 10) + .foregroundColor(Color("ALUM Primary Purple")) + .frame(maxHeight: .infinity, alignment: .top) + } + } + } + } + } +} + +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") + ) { + MenteeCard(isEmpty: true, uID: viewModel.mentor!.menteeIds![index]) + .padding(.bottom, 15) + .padding(.trailing, 10) } } + .padding(.bottom, 30) + .padding(.leading, 16) + .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 { static var previews: some View { - MentorProfileScreen(uID: "6431b9a2bcf4420fe9825fe5") + 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" + ) + } + } + } } } diff --git a/ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift similarity index 93% rename from ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift rename to ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift index ed999dac..48a2e404 100644 --- a/ALUM/ALUM/Views/PreSessionForm/MissedSessionScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/MissedSessionScreen.swift @@ -18,16 +18,17 @@ 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 { @@ -53,7 +54,6 @@ struct MissedSessionScreen: View { } } .edgesIgnoringSafeArea(.bottom) - .applyMissedSessionScreenHeaderModifier() } var footer: some View { @@ -83,7 +83,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 +98,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 +179,12 @@ 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" + ) } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift similarity index 93% rename from ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift rename to ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift index 86c952b3..82e532f6 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionConfirmationScreen.swift +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionConfirmationScreen.swift @@ -9,8 +9,8 @@ import SwiftUI struct PostSessionConfirmationScreen: View { @ObservedObject var viewModel: QuestionViewModel - @Environment(\.dismiss) var dismiss - @State var notesID: String = "" + + @State var notesID: String @State var currNotes: String = "this" // "this" or "other" func setMyNotes() { @@ -22,11 +22,7 @@ struct PostSessionConfirmationScreen: View { } var body: some View { - VStack { - StaticProgressBarComponent(nodes: viewModel.questionList.count, - filledNodes: viewModel.questionList.count, activeNode: 0) - .background(Color.white) - + return VStack { ScrollView { content } @@ -37,13 +33,12 @@ struct PostSessionConfirmationScreen: View { .background(Rectangle().fill(Color.white).shadow(radius: 8)) } .edgesIgnoringSafeArea(.bottom) - .applyPostSessionScreenHeaderModifier() } var footer: some View { HStack { Button { - dismiss() + viewModel.prevQuestion() } label: { HStack { Image(systemName: "arrow.left") @@ -68,7 +63,8 @@ struct PostSessionConfirmationScreen: View { Text("Save") } .buttonStyle(FilledInButtonStyle()) - NavigationLink(destination: SessionConfirmationScreen( + + NavigationLink(destination: ConfirmationScreen( text: ["Post-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), isActive: $viewModel.submitSuccess) { @@ -198,6 +194,6 @@ struct PostSessionConfirmationScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - PostSessionConfirmationScreen(viewModel: viewModel) + 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..84c5356d --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionFormRouter.swift @@ -0,0 +1,84 @@ +// +// 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, + noteId: notesID + ) + } 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..674fd927 --- /dev/null +++ b/ALUM/ALUM/Views/PostSessionForm/PostSessionQuestionScreen.swift @@ -0,0 +1,174 @@ +// +// MenteePostSessionQuestionScreen.swift +// ALUM +// +// Created by Jenny Mar on 4/5/23. +// + +import SwiftUI + +struct PostSessionQuestionScreen: View { + + @ObservedObject var viewModel: QuestionViewModel + + var otherUser: String + var date: String + var time: String + var noteId: 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, + notesID: noteId, + date: date, + time: time, + otherUser: otherUser + ), + 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/PreSessionForm/PostSessionView.swift b/ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift similarity index 90% rename from ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift rename to ALUM/ALUM/Views/PostSessionForm/PostSessionView.swift index b3134380..cbf31a55 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PostSessionView.swift +++ b/ALUM/ALUM/Views/PostSessionForm/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 ) @@ -68,7 +68,6 @@ struct PostSessionView: View { if !viewModel.isLoading { PostSessionQuestionScreen( viewModel: viewModel, - notesID: notesID, otherUser: otherName, date: date, time: time @@ -80,9 +79,9 @@ struct PostSessionView: View { .onAppear { Task { do { - try await viewModel.loadPostNotes(notesID: notesID, otherNotesID: otherNotesID) + try await viewModel.fetchPostSessionNotes(notesId: notesID, otherNotesId: otherNotesID) } catch { - print("Error") + print("Error \(error)") } } } diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift b/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift similarity index 75% rename from ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift rename to ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift index 7e24939a..b6307b23 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPostSessionNotesPage.swift +++ b/ALUM/ALUM/Views/PostSessionForm/ViewPostSessionNotesPage.swift @@ -7,39 +7,16 @@ 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 otherName: String = "" - @State var date: String = "" - @State var time: String = "" + @State var currNotes: String = "this" // "this" or "other" func setMyNotes() { currNotes = "this" @@ -50,30 +27,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 +52,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, + return CustomNavLink( + destination: + PostSessionFormRouter( notesID: notesID, - otherUser: otherName, - date: date, time: time - ) - } label: { + 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 +205,12 @@ 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/PreSessionForm/PreSessionConfirmationScreen.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionConfirmationScreen.swift index 91943915..c22bf025 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,10 +51,12 @@ struct PreSessionConfirmationScreen: View { } } } label: { - Text("Save") + ALUMText(text: "Save", textColor: ALUMColor.white) } .buttonStyle(FilledInButtonStyle()) - NavigationLink(destination: SessionConfirmationScreen( + + // Custom Nav Link not needed here + NavigationLink(destination: ConfirmationScreen( text: ["Pre-session form saved!", "You can continue on the notes later under \"Sessions\".", "Great"]), isActive: $viewModel.submitSuccess) { @@ -135,6 +131,6 @@ struct PreSessionConfirmationScreen_Previews: PreviewProvider { static private var viewModel = QuestionViewModel() static var previews: some View { - PreSessionConfirmationScreen(viewModel: viewModel) + PreSessionConfirmationScreen(viewModel: viewModel, notesID: "646a6e164082520f4fcf2f92") } } diff --git a/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift b/ALUM/ALUM/Views/PreSessionForm/PreSessionFormRouter.swift new file mode 100644 index 00000000..e87da5c0 --- /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.fetchPreSessionNotes(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..e240d30b 100644 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift +++ b/ALUM/ALUM/Views/PreSessionForm/PreSessionQuestionScreen.swift @@ -10,152 +10,133 @@ 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() } + 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 { + firstQuestionBanner + } + + 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" { + 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) + } +} + +// Footer Code goes here +extension PreSessionQuestionScreen { @ViewBuilder var footer: some View { - if !viewModel.lastQuestion { + Group { 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()) + footerForFirstQuestion } 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()) - } + footerWithBackAndContinue } - } else { + } + .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 { - 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) - } - } - ) - .buttonStyle(FilledInButtonStyle()) + ALUMText(text: "Continue", textColor: ALUMColor.white) + Image(systemName: "arrow.right") + .font(.system(size: 17)) + .foregroundColor(Color.white) } } + .buttonStyle(FilledInButtonStyle()) } - // viewModel.questionList[index].answerBullet - var content: some View { - VStack { - 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)) + 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 deleted file mode 100644 index 644d36b9..00000000 --- a/ALUM/ALUM/Views/PreSessionForm/PreSessionView.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// PreSessionView.swift -// ALUM -// -// Created by Neelam Gurnani on 3/9/23. -// - -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() - - @State var notesID: String = "" - - @State var otherName: String = "" - @State var date: String = "" - @State var time: String = "" - - var body: some View { - print(viewModel.isLoading) - 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("") - .navigationBarHidden(true) - .onAppear { - Task { - do { - try await viewModel.loadNotes(notesID: notesID) - } catch { - print("Error") - } - } - // viewModel.loadTestData() - } - } - } - // .navigationBarBackButtonHidden() - - } -} - -struct PreSessionView_Previews: PreviewProvider { - static var previews: some View { - PreSessionView() - } -} diff --git a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift b/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift similarity index 63% rename from ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift rename to ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift index cb835d77..2a78447d 100644 --- a/ALUM/ALUM/Views/SessionDetailsView/ViewPreSessionNotesPage.swift +++ b/ALUM/ALUM/Views/PreSessionForm/ViewPreSessionNotesPage.swift @@ -7,64 +7,32 @@ 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 { - @StateObject var viewModel = QuestionViewModel() - - @State var notesID: String = "" + var allowEditing: Bool + var notesID: String + var otherName: String + var date: String = "" + var time: String = "" - @State var otherName: String = "" - @State var date: String = "" - @State 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 { - VStack { - ScrollView { - content - } - - if viewModel.currentUser.role == UserRole.mentee { - 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.fetchPreSessionNotes(noteId: notesID) } catch { print("Error") } @@ -72,22 +40,39 @@ struct ViewPreSessionNotesPage: View { } } - 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 { + 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 { @@ -155,6 +140,6 @@ struct ViewPreSessionNotesPage: View { struct ViewPreSessionNotesPage_Previews: PreviewProvider { static var previews: some View { - ViewPreSessionNotesPage() + ViewPreSessionNotesPage(allowEditing: false, notesID: "", otherName: "mentor") } } diff --git a/ALUM/ALUM/Views/Routers/HomeTabRouter.swift b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift new file mode 100644 index 00000000..30d5a1b7 --- /dev/null +++ b/ALUM/ALUM/Views/Routers/HomeTabRouter.swift @@ -0,0 +1,31 @@ +// +// 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 new file mode 100644 index 00000000..94036e6a --- /dev/null +++ b/ALUM/ALUM/Views/Routers/LoggedInRouter.swift @@ -0,0 +1,115 @@ +// +// CustomTabView.swift +// ALUM +// +// Created by Aman Aggarwal on 5/1/23. +// + +import SwiftUI + +// Contains the custom tab view +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 + } + + let tabItems = [ + 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 + // 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 { + tabsDisplay + } + } + content + .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 + default: + Text("Error") + } + + } + .onAppear(perform: { + currentUser.showTabBar = true + }) + .onDisappear(perform: { + currentUser.showTabBar = false + }) + } + } + + var tabsDisplay: some View { + ZStack(alignment: .bottom) { + HStack(spacing: 0) { + ForEach(0.. 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() - } -} 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 f8d0450b..08431286 100644 --- a/backend/src/errors/internal.ts +++ b/backend/src/errors/internal.ts @@ -15,6 +15,9 @@ 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"; + export class InternalError extends CustomError { static ERROR_GETTING_MENTEE = new InternalError(0, 500, ERROR_GETTING_MENTEE); @@ -39,4 +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); } diff --git a/backend/src/errors/service.ts b/backend/src/errors/service.ts index 399b2786..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"; export class ServiceError extends CustomError { static IMAGE_NOT_SAVED = new ServiceError(0, 404, IMAGE_NOT_SAVED); @@ -40,4 +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/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 6d11360c..7e57e63f 100644 --- a/backend/src/models/mentor.ts +++ b/backend/src/models/mentor.ts @@ -4,6 +4,7 @@ * be stored on firebase */ import mongoose from "mongoose"; +import { UserStatusType } from "../types"; interface MentorInterface { name: string; @@ -19,7 +20,7 @@ interface MentorInterface { topicsOfExpertise: string[]; mentorMotivation: string; pairingIds: string[]; - status: string; + status: UserStatusType; personalAccessToken: string; location: string; } @@ -38,7 +39,7 @@ interface MentorDoc extends mongoose.Document { topicsOfExpertise: string[]; mentorMotivation: string; pairingIds: string[]; - status: string; + status: UserStatusType; personalAccessToken: string; location: string; } @@ -64,10 +65,6 @@ const mentorSchema = new mongoose.Schema({ type: String, required: true, }, - zoomLink: { - type: String, - required: true, - }, graduationYear: { type: Number, required: true, @@ -106,6 +103,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 d86b8f22..631fc70c 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/notes.ts b/backend/src/routes/notes.ts index dbe4252e..f89b9f00 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); diff --git a/backend/src/routes/sessions.ts b/backend/src/routes/sessions.ts index 4e9db243..ee376a10 100644 --- a/backend/src/routes/sessions.ts +++ b/backend/src/routes/sessions.ts @@ -13,6 +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"; /** * This is a post route to create a new session. @@ -97,17 +98,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; } @@ -116,6 +109,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 +135,11 @@ 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: { @@ -139,13 +149,17 @@ router.get( missedSessionReason, 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/routes/user.ts b/backend/src/routes/user.ts index 53e9bb9f..9a6a9d61 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, getLastSession } from "../services/session"; const router = express.Router(); @@ -269,7 +271,7 @@ router.get( career, graduationYear, calendlyLink, - zoomLink, + location, topicsOfExpertise, pairingIds, mentorMotivation, @@ -302,7 +304,7 @@ router.get( personalAccessToken: personalAccessToken ? "[REDACTED]" : null, graduationYear, calendlyLink, - zoomLink, + zoomLink: location, topicsOfExpertise, whyPaired, }, @@ -321,8 +323,8 @@ router.get( imageId, about, calendlyLink, - zoomLink, personalAccessToken: personalAccessToken ? "[REDACTED]" : null, + zoomLink: location, graduationYear, college, major, @@ -343,7 +345,7 @@ router.get( about, calendlyLink, personalAccessToken: personalAccessToken ? "[REDACTED]" : null, - zoomLink: zoomLink === undefined ? "N/A" : zoomLink, + zoomLink: location ?? "N/A", graduationYear, college, major, @@ -368,4 +370,93 @@ 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); + 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") { + res.status(200).send({ + status: mentee.status, + }); + } + const getPairedMentorIdPromise = getMentorId(mentee.pairingId); + const [upcomingSessionId, pastSessionId, pairedMentorId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getPairedMentorIdPromise, + ]); + console.log({ + status: mentee.status, + sessionId: upcomingSessionId ?? pastSessionId, + pairedMentorId, + }); + res.status(200).send({ + status: mentee.status, + sessionId: upcomingSessionId ?? pastSessionId, + 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 [upcomingSessionId, pastSessionId, pairedMenteeId] = await Promise.all([ + getUpcomingSessionPromise, + getPastSessionPromise, + getMenteeIdsPromise, + ]); + + res.status(200).send({ + status: mentor.status, + sessionId: upcomingSessionId ?? pastSessionId, + 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/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; } diff --git a/backend/src/services/session.ts b/backend/src/services/session.ts new file mode 100644 index 00000000..e38c43f2 --- /dev/null +++ b/backend/src/services/session.ts @@ -0,0 +1,112 @@ +import { Types } from "mongoose"; +import { InternalError, ServiceError } from "../errors"; +import { Session } 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; + } +} + +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: + * [ + * "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)); diff --git a/backend/src/types/user.ts b/backend/src/types/user.ts index 56362432..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 CreateMenteeRequestBodyType = Infer; export type CreateMentorRequestBodyType = Infer;