From 4fd2c952af8e081601116b79892ed4d15b31a171 Mon Sep 17 00:00:00 2001 From: stevepodell Date: Mon, 17 Feb 2025 10:11:29 -0800 Subject: [PATCH 1/2] Added PermissionsAdministration. isAdmin in sql controls access, and limits ability to grant further permissions. Sign in via email/password with email verification is working well. Apis are now protected from access without cookie/sessionID. ENABLE_REACT_QUERY_TOOLS becomes much more important. Commented out more code we won't be using to make searching easier. Cleared more lint warnings. --- src/js/actions/VoterActions.js | 994 +++++++++--------- src/js/actions/VoterSessionActions.js | 82 +- src/js/components/Navigation/HeaderBar.jsx | 5 +- .../Person/AddPersonDrawerMainContent.jsx | 5 +- src/js/components/Person/EditPersonForm.jsx | 2 - src/js/components/PrivateRoute.jsx | 3 +- src/js/contexts/ConnectAppContext.jsx | 5 +- src/js/contexts/contextFunctions.jsx | 8 + src/js/pages/Login.jsx | 38 +- .../PermissionsAdministration.jsx | 347 ++++++ .../pages/SystemSettings/SystemSettings.jsx | 14 +- src/js/pages/TeamHome.jsx | 1 + src/js/react-query/WeConnectQuery.js | 15 +- src/js/utils/safeLoadConfig.js | 5 + 14 files changed, 949 insertions(+), 575 deletions(-) create mode 100644 src/js/contexts/contextFunctions.jsx create mode 100644 src/js/pages/SystemSettings/PermissionsAdministration.jsx create mode 100644 src/js/utils/safeLoadConfig.js diff --git a/src/js/actions/VoterActions.js b/src/js/actions/VoterActions.js index 7d0d2c7..097c944 100644 --- a/src/js/actions/VoterActions.js +++ b/src/js/actions/VoterActions.js @@ -1,497 +1,497 @@ -import Dispatcher from '../common/dispatcher/Dispatcher'; -import { isCordova } from '../common/utils/isCordovaOrWebApp'; // eslint-disable-line import/no-cycle -import AppObservableStore from '../stores/AppObservableStore'; // eslint-disable-line import/no-cycle -import arrayContains from '../common/utils/arrayContains'; - -export default { - clearEmailAddressStatus () { - Dispatcher.dispatch({ type: 'clearEmailAddressStatus', payload: true }); - }, - - clearSecretCodeVerificationStatus () { - Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatus', payload: true }); - }, - - clearSecretCodeVerificationStatusAndEmail () { - Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatusAndEmail', payload: true }); - }, - - clearSecretCodeVerificationStatusAndPhone () { - Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatusAndPhone', payload: true }); - }, - - clearSMSPhoneNumberStatus () { - Dispatcher.dispatch({ type: 'clearSMSPhoneNumberStatus', payload: true }); - }, - - clearVoterElectionId () { - Dispatcher.dispatch({ type: 'clearVoterElectionId', payload: true }); - }, - - clearVoterContactEmailImportVariables () { - Dispatcher.dispatch({ type: 'clearVoterContactEmailImportVariables', payload: true }); - }, - - organizationSuggestionTasks (kindOfSuggestionTask, kindOfFollowTask) { - Dispatcher.loadEndpoint('organizationSuggestionTasks', - { - kind_of_suggestion_task: kindOfSuggestionTask, - kind_of_follow_task: kindOfFollowTask, - }); - }, - - positionListForVoter (showOnlyThisElection, showAllOtherElections) { - Dispatcher.loadEndpoint('positionListForVoter', - { - show_only_this_election: showOnlyThisElection, - show_all_other_elections: showAllOtherElections, - }); - }, - - removeVoterEmailAddress (emailWeVoteId) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - email_we_vote_id: emailWeVoteId, - delete_email: true, - }); - }, - - removeVoterSMSPhoneNumber (smsWeVoteId) { - Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { - sms_we_vote_id: smsWeVoteId, - delete_sms: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - // Send the sign in link to their email address - // We keep this in place for historical purposes, since people who haven't - // updated their apps are still using this function - sendSignInLinkEmail (voterEmailAddress) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - text_for_email_address: voterEmailAddress, - send_link_to_sign_in: true, - make_primary_email: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - // This is for sending a 6 digit code that the voter enters in the same - // interface where the code is requested - sendSignInCodeEmail (voterEmailAddress) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - text_for_email_address: voterEmailAddress, - send_sign_in_code_email: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - // This is for sending a 6 digit code that the voter enters in the same - // interface where the code is requested - sendSignInCodeSMS (voterSMSPhoneNumber) { - Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { - sms_phone_number: voterSMSPhoneNumber, - send_sign_in_code_sms: true, - make_primary_sms_phone_number: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - sendVerificationEmail (voterEmailWeVoteId) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - email_we_vote_id: voterEmailWeVoteId, - resend_verification_email: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - setAsPrimaryEmailAddress (emailWeVoteId) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - email_we_vote_id: emailWeVoteId, - make_primary_email: true, - }); - }, - - setAsPrimarySMSPhoneNumber (smsWeVoteId) { - Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { - sms_we_vote_id: smsWeVoteId, - make_primary_sms_phone_number: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - setExternalVoterId (externalVoterId) { - Dispatcher.dispatch({ type: 'setExternalVoterId', payload: externalVoterId }); - }, - - twitterRetrieveIdsIfollow () { - Dispatcher.loadEndpoint('twitterRetrieveIdsIFollow', {}); - }, - - voterAccountDelete (deleteVoterAccount = false) { - Dispatcher.loadEndpoint('voterUpdate', { - delete_voter_account: deleteVoterAccount, - }); - }, - - voterAddressRetrieve (id) { - // console.log("VoterActions, voterAddressRetrieve"); - Dispatcher.loadEndpoint('voterAddressRetrieve', { voter_device_id: id }); - }, - - voterAddressOnlyRetrieve (id) { - // console.log("VoterActions, voterAddressOnlyRetrieve"); - // Note: This calls the voterAddressRetrieve API without the cascading requests that have been added to the root voterAddressRetrieve endpoint. - // See Dispatcher.js at endpointAdjusted - Dispatcher.loadEndpoint('voterAddressOnlyRetrieve', { voter_device_id: id }); - }, - - voterAddressSave (text, simple_save = false, google_civic_election_id = 0) { - // console.log('VoterActions voterAddressSave, text:', text); - Dispatcher.loadEndpoint('voterAddressSave', { text_for_map_search: text, simple_save, google_civic_election_id }); - }, - - voterAnalysisForJumpProcess (incomingVoterDeviceId) { - Dispatcher.loadEndpoint('voterAnalysisForJumpProcess', { - incoming_voter_device_id: incomingVoterDeviceId, - }); - }, - - voterCompleteYourProfileSave (firstName = '', firstNameChanged = false, lastName = '', lastNameChanged = false, voterPhotoQueuedToSave = '', voterPhotoQueuedToSaveSet = false) { - Dispatcher.loadEndpoint('voterUpdate', - { - first_name: firstName, - first_name_changed: firstNameChanged, - last_name: lastName, - last_name_changed: lastNameChanged, - voter_photo_from_file_reader: voterPhotoQueuedToSave, - voter_photo_changed: voterPhotoQueuedToSaveSet, - }); - }, - - voterContactIgnore (emailAddressText, otherVoterWeVoteId = '') { - Dispatcher.loadEndpoint('voterContactSave', { - email_address_text: emailAddressText, - ignore_voter_contact: true, - other_voter_we_vote_id: otherVoterWeVoteId, - }); - }, - - voterContactStopIgnoring (emailAddressText, otherVoterWeVoteId = '') { - Dispatcher.loadEndpoint('voterContactSave', { - email_address_text: emailAddressText, - stop_ignoring_voter_contact: true, - other_voter_we_vote_id: otherVoterWeVoteId, - }); - }, - - voterContactListAugmentWithLocation (augmentWithLocation = false) { - Dispatcher.loadEndpoint('voterContactListSave', { - augment_voter_contact_emails_with_location: augmentWithLocation, - }); - }, - - voterContactListAugmentWithWeVoteData (augmentWithWeVoteData = false) { - Dispatcher.loadEndpoint('voterContactListSave', { - augment_voter_contact_emails_with_we_vote_data: augmentWithWeVoteData, - }); - }, - - voterContactListDelete (deleteAllVoterContactEmails = false) { - Dispatcher.loadEndpoint('voterContactListSave', { - delete_all_voter_contact_emails: deleteAllVoterContactEmails, - }); - }, - - voterContactListRetrieve () { - Dispatcher.loadEndpoint('voterContactListRetrieve', {}); - }, - - voterContactListSave (contacts, fromGooglePeopleApi = false) { - const contactsString = JSON.stringify(contacts); - // console.log('voterContactListSave contacts: ', contactsString); - Dispatcher.loadEndpoint('voterContactListSave', { - contacts: contactsString, - from_google_people_api: fromGooglePeopleApi, - google_api_key_type: 'ballot', - }); - }, - - voterEmailAddressRetrieve () { - Dispatcher.loadEndpoint('voterEmailAddressRetrieve', {}); - }, - - voterEmailAddressSave (voterEmailAddress, send_link_to_sign_in = false) { - Dispatcher.loadEndpoint('voterEmailAddressSave', { - text_for_email_address: voterEmailAddress, - send_link_to_sign_in, - make_primary_email: true, - is_cordova: isCordova(), - hostname: AppObservableStore.getHostname(), - }); - }, - - voterEmailAddressSignIn (emailSecretKey) { - Dispatcher.loadEndpoint('voterEmailAddressSignIn', { - email_secret_key: emailSecretKey, - }); - }, - - voterEmailAddressSignInConfirm (emailSecretKey) { - Dispatcher.loadEndpoint('voterEmailAddressSignIn', { - email_secret_key: emailSecretKey, - yes_please_merge_accounts: true, - }); - }, - - voterEmailAddressVerify (emailSecretKey, firstName = '', lastName = '', fullName = '') { - Dispatcher.loadEndpoint('voterEmailAddressVerify', { - email_secret_key: emailSecretKey, - first_name: firstName, - first_name_changed: firstName && firstName !== '', - last_name: lastName, - last_name_changed: lastName && lastName !== '', - full_name: fullName, - full_name_changed: fullName && fullName !== '', - name_save_only_if_no_existing_names: true, - }); - }, - - voterEmailQueuedToSave (voterEmail) { - Dispatcher.dispatch({ type: 'voterEmailQueuedToSave', payload: voterEmail }); - }, - - voterExternalIdSave (externalVoterId, membershipOrganizationWeVoteId) { - Dispatcher.loadEndpoint('voterUpdate', - { - external_voter_id: externalVoterId, - membership_organization_we_vote_id: membershipOrganizationWeVoteId, - }); - }, - - voterFacebookSaveToCurrentAccount () { - Dispatcher.loadEndpoint('voterFacebookSaveToCurrentAccount', { - }); - }, - - voterFirstNameQueuedToSave (firstName) { - Dispatcher.dispatch({ type: 'voterFirstNameQueuedToSave', payload: firstName }); - }, - - // Tell the server to only save this name if a name does not currently exist - voterFullNameSoftSave (firstName, lastName, fullName = '') { - Dispatcher.loadEndpoint('voterUpdate', - { - first_name: firstName, - first_name_changed: firstName && firstName !== '', - last_name: lastName, - last_name_changed: lastName && lastName !== '', - full_name: fullName, - full_name_changed: fullName && fullName !== '', - name_save_only_if_no_existing_names: true, - }); - }, - - voterLastNameQueuedToSave (lastName) { - Dispatcher.dispatch({ type: 'voterLastNameQueuedToSave', payload: lastName }); - }, - - voterMergeTwoAccountsByEmailKey (emailSecretKey, doNotMergeIfCurrentlySignedIn = false) { - Dispatcher.loadEndpoint('voterMergeTwoAccounts', - { - do_not_merge_if_currently_signed_in: doNotMergeIfCurrentlySignedIn, - email_secret_key: emailSecretKey, - facebook_secret_key: '', - incoming_voter_device_id: '', - invitation_secret_key: '', - twitter_secret_key: '', - hostname: AppObservableStore.getHostname(), - }); - }, - - voterMergeTwoAccountsByFacebookKey (facebookSecretKey) { - // console.log("VoterActions, voterMergeTwoAccountsByFacebookKey"); - Dispatcher.loadEndpoint('voterMergeTwoAccounts', - { - email_secret_key: '', - facebook_secret_key: facebookSecretKey, - incoming_voter_device_id: '', - invitation_secret_key: '', - twitter_secret_key: '', - hostname: AppObservableStore.getHostname(), - }); - }, - - voterMergeTwoAccountsByInvitationKey (invitationSecretKey) { - Dispatcher.loadEndpoint('voterMergeTwoAccounts', - { - email_secret_key: '', - facebook_secret_key: '', - incoming_voter_device_id: '', - invitation_secret_key: invitationSecretKey, - twitter_secret_key: '', - hostname: AppObservableStore.getHostname(), - }); - }, - - voterMergeTwoAccountsByJumpProcess (incomingVoterDeviceId) { - // TODO DALE 2018-01-10 voterMergeTwoAccounts doesn't support incomingVoterDeviceId yet - Dispatcher.loadEndpoint('voterMergeTwoAccounts', - { - email_secret_key: '', - facebook_secret_key: '', - incoming_voter_device_id: incomingVoterDeviceId, - invitation_secret_key: '', - twitter_secret_key: '', - hostname: AppObservableStore.getHostname(), - }); - }, - - voterMergeTwoAccountsByTwitterKey (twitterSecretKey) { - Dispatcher.loadEndpoint('voterMergeTwoAccounts', - { - email_secret_key: '', - facebook_secret_key: '', - incoming_voter_device_id: '', - invitation_secret_key: '', - twitter_secret_key: twitterSecretKey, - hostname: AppObservableStore.getHostname(), - }); - }, - - voterNameSave (firstName, lastName) { - Dispatcher.loadEndpoint('voterUpdate', - { - first_name: firstName, - first_name_changed: true, - last_name: lastName, - last_name_changed: true, - }); - }, - - voterPhotoDelete () { - Dispatcher.loadEndpoint('voterUpdate', - { - voter_photo_from_file_reader: '', - voter_photo_changed: true, - }); - }, - - voterPhotoQueuedToSave (voterPhotoFromFileReader) { - Dispatcher.dispatch({ type: 'voterPhotoQueuedToSave', payload: voterPhotoFromFileReader }); - }, - - voterPhotoSave (voterPhotoQueuedToSave = '', voterPhotoQueuedToSaveSet = false, profileImageTypeCurrentlyActive = '') { - // console.log('VoterActions voterPhotoSave'); - const profileImageTypeCurrentlyActiveSet = arrayContains(profileImageTypeCurrentlyActive, ['FACEBOOK', 'TWITTER', 'UPLOADED']); - Dispatcher.loadEndpoint('voterUpdate', - { - profile_image_type_currently_active: profileImageTypeCurrentlyActive, - profile_image_type_currently_active_changed: profileImageTypeCurrentlyActiveSet, - voter_photo_from_file_reader: voterPhotoQueuedToSave, - voter_photo_changed: voterPhotoQueuedToSaveSet, - }); - }, - - voterPhotoTooBigReset () { - Dispatcher.dispatch({ type: 'voterPhotoTooBigReset', payload: true }); - }, - - voterRetrieve (mergeFromVoterWeVoteId = '', mergeToVoterWeVoteId = '') { - // console.log('VoterActions, voterRetrieve mergeFromVoterWeVoteId:', mergeFromVoterWeVoteId, ', mergeToVoterWeVoteId:', mergeToVoterWeVoteId); - if (mergeFromVoterWeVoteId && mergeToVoterWeVoteId) { - Dispatcher.loadEndpoint('voterRetrieve', { - merge_from_voter_we_vote_id: mergeFromVoterWeVoteId, - merge_to_voter_we_vote_id: mergeToVoterWeVoteId, - }); - } else { - Dispatcher.loadEndpoint('voterRetrieve'); - } - }, - - voterSignOut (signOutAllDevices = false) { - Dispatcher.loadEndpoint('voterSignOut', { - sign_out_all_devices: signOutAllDevices, - }); - }, - - voterSMSPhoneNumberRetrieve () { - Dispatcher.loadEndpoint('voterSMSPhoneNumberRetrieve', {}); - }, - - voterSMSPhoneNumberSave (smsPhoneNumber) { - Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { - sms_phone_number: smsPhoneNumber, - make_primary_sms_phone_number: true, - hostname: AppObservableStore.getHostname(), - }); - }, - - voterSplitIntoTwoAccounts () { - Dispatcher.loadEndpoint('voterSplitIntoTwoAccounts', - { - split_off_twitter: true, - }); - }, - - voterTwitterSaveToCurrentAccount () { - Dispatcher.loadEndpoint('voterTwitterSaveToCurrentAccount', { - }); - }, - - voterUpdateInterfaceStatusFlags (flagIntegerToSet) { - Dispatcher.loadEndpoint('voterUpdate', - { - flag_integer_to_set: flagIntegerToSet, - }); - }, - - voterUpdateNotificationSettingsFlags (flagIntegerToSet, flagIntegerToUnset = '') { - Dispatcher.loadEndpoint('voterUpdate', - { - notification_flag_integer_to_set: flagIntegerToSet, - notification_flag_integer_to_unset: flagIntegerToUnset, - }); - }, - - voterNotificationSettingsUpdateFromSecretKey (emailSubscriptionSecretKey = '', smsSubscriptionSecretKey = '', flagIntegerToSet = 0, flagIntegerToSetChanged = false, flagIntegerToUnset = 0, flagIntegerToUnsetChanged = false) { - Dispatcher.loadEndpoint('voterNotificationSettingsUpdate', - { - email_subscription_secret_key: emailSubscriptionSecretKey, - sms_subscription_secret_key: smsSubscriptionSecretKey, - notification_flag_integer_to_set: flagIntegerToSet, - notification_flag_integer_to_set_changed: flagIntegerToSetChanged, - notification_flag_integer_to_unset: flagIntegerToUnset, - notification_flag_integer_to_unset_changed: flagIntegerToUnsetChanged, - }); - }, - - voterVerifySecretCode (secretCode, codeSentToSMSPhoneNumber) { - // console.log('VoterActions, voterVerifySecretCode codeSentToSMSPhoneNumber:', codeSentToSMSPhoneNumber); - Dispatcher.loadEndpoint('voterVerifySecretCode', { - secret_code: secretCode, - code_sent_to_sms_phone_number: codeSentToSMSPhoneNumber, - }); - }, - - voterAppleSignInSave (email, givenName, middleName, familyName, user, identityToken) { - // eslint-disable-next-line camelcase - const { device: { platform: apple_platform, version: apple_os_version, model: apple_model } } = window; - Dispatcher.loadEndpoint('appleSignInSave', { - email, - first_name: givenName, - middle_name: middleName, - last_name: familyName, - user_code: user, - apple_platform, - apple_os_version, - apple_model, - identity_token: identityToken, - }); - }, - - deviceStoreFirebaseCloudMessagingToken (firebaseFCMToken) { - Dispatcher.loadEndpoint('deviceStoreFirebaseCloudMessagingToken', { - firebase_fcm_token: firebaseFCMToken, - }); - }, -}; +// import Dispatcher from '../common/dispatcher/Dispatcher'; +// import { isCordova } from '../common/utils/isCordovaOrWebApp'; // eslint-disable-line import/no-cycle +// import AppObservableStore from '../stores/AppObservableStore'; // eslint-disable-line import/no-cycle +// import arrayContains from '../common/utils/arrayContains'; +// +// export default { +// clearEmailAddressStatus () { +// Dispatcher.dispatch({ type: 'clearEmailAddressStatus', payload: true }); +// }, +// +// clearSecretCodeVerificationStatus () { +// Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatus', payload: true }); +// }, +// +// clearSecretCodeVerificationStatusAndEmail () { +// Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatusAndEmail', payload: true }); +// }, +// +// clearSecretCodeVerificationStatusAndPhone () { +// Dispatcher.dispatch({ type: 'clearSecretCodeVerificationStatusAndPhone', payload: true }); +// }, +// +// clearSMSPhoneNumberStatus () { +// Dispatcher.dispatch({ type: 'clearSMSPhoneNumberStatus', payload: true }); +// }, +// +// clearVoterElectionId () { +// Dispatcher.dispatch({ type: 'clearVoterElectionId', payload: true }); +// }, +// +// clearVoterContactEmailImportVariables () { +// Dispatcher.dispatch({ type: 'clearVoterContactEmailImportVariables', payload: true }); +// }, +// +// organizationSuggestionTasks (kindOfSuggestionTask, kindOfFollowTask) { +// Dispatcher.loadEndpoint('organizationSuggestionTasks', +// { +// kind_of_suggestion_task: kindOfSuggestionTask, +// kind_of_follow_task: kindOfFollowTask, +// }); +// }, +// +// positionListForVoter (showOnlyThisElection, showAllOtherElections) { +// Dispatcher.loadEndpoint('positionListForVoter', +// { +// show_only_this_election: showOnlyThisElection, +// show_all_other_elections: showAllOtherElections, +// }); +// }, +// +// removeVoterEmailAddress (emailWeVoteId) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// email_we_vote_id: emailWeVoteId, +// delete_email: true, +// }); +// }, +// +// removeVoterSMSPhoneNumber (smsWeVoteId) { +// Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { +// sms_we_vote_id: smsWeVoteId, +// delete_sms: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// // Send the sign in link to their email address +// // We keep this in place for historical purposes, since people who haven't +// // updated their apps are still using this function +// sendSignInLinkEmail (voterEmailAddress) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// text_for_email_address: voterEmailAddress, +// send_link_to_sign_in: true, +// make_primary_email: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// // This is for sending a 6 digit code that the voter enters in the same +// // interface where the code is requested +// sendSignInCodeEmail (voterEmailAddress) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// text_for_email_address: voterEmailAddress, +// send_sign_in_code_email: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// // This is for sending a 6 digit code that the voter enters in the same +// // interface where the code is requested +// sendSignInCodeSMS (voterSMSPhoneNumber) { +// Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { +// sms_phone_number: voterSMSPhoneNumber, +// send_sign_in_code_sms: true, +// make_primary_sms_phone_number: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// sendVerificationEmail (voterEmailWeVoteId) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// email_we_vote_id: voterEmailWeVoteId, +// resend_verification_email: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// setAsPrimaryEmailAddress (emailWeVoteId) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// email_we_vote_id: emailWeVoteId, +// make_primary_email: true, +// }); +// }, +// +// setAsPrimarySMSPhoneNumber (smsWeVoteId) { +// Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { +// sms_we_vote_id: smsWeVoteId, +// make_primary_sms_phone_number: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// setExternalVoterId (externalVoterId) { +// Dispatcher.dispatch({ type: 'setExternalVoterId', payload: externalVoterId }); +// }, +// +// twitterRetrieveIdsIfollow () { +// Dispatcher.loadEndpoint('twitterRetrieveIdsIFollow', {}); +// }, +// +// voterAccountDelete (deleteVoterAccount = false) { +// Dispatcher.loadEndpoint('voterUpdate', { +// delete_voter_account: deleteVoterAccount, +// }); +// }, +// +// voterAddressRetrieve (id) { +// // console.log("VoterActions, voterAddressRetrieve"); +// Dispatcher.loadEndpoint('voterAddressRetrieve', { voter_device_id: id }); +// }, +// +// voterAddressOnlyRetrieve (id) { +// // console.log("VoterActions, voterAddressOnlyRetrieve"); +// // Note: This calls the voterAddressRetrieve API without the cascading requests that have been added to the root voterAddressRetrieve endpoint. +// // See Dispatcher.js at endpointAdjusted +// Dispatcher.loadEndpoint('voterAddressOnlyRetrieve', { voter_device_id: id }); +// }, +// +// voterAddressSave (text, simple_save = false, google_civic_election_id = 0) { +// // console.log('VoterActions voterAddressSave, text:', text); +// Dispatcher.loadEndpoint('voterAddressSave', { text_for_map_search: text, simple_save, google_civic_election_id }); +// }, +// +// voterAnalysisForJumpProcess (incomingVoterDeviceId) { +// Dispatcher.loadEndpoint('voterAnalysisForJumpProcess', { +// incoming_voter_device_id: incomingVoterDeviceId, +// }); +// }, +// +// voterCompleteYourProfileSave (firstName = '', firstNameChanged = false, lastName = '', lastNameChanged = false, voterPhotoQueuedToSave = '', voterPhotoQueuedToSaveSet = false) { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// first_name: firstName, +// first_name_changed: firstNameChanged, +// last_name: lastName, +// last_name_changed: lastNameChanged, +// voter_photo_from_file_reader: voterPhotoQueuedToSave, +// voter_photo_changed: voterPhotoQueuedToSaveSet, +// }); +// }, +// +// voterContactIgnore (emailAddressText, otherVoterWeVoteId = '') { +// Dispatcher.loadEndpoint('voterContactSave', { +// email_address_text: emailAddressText, +// ignore_voter_contact: true, +// other_voter_we_vote_id: otherVoterWeVoteId, +// }); +// }, +// +// voterContactStopIgnoring (emailAddressText, otherVoterWeVoteId = '') { +// Dispatcher.loadEndpoint('voterContactSave', { +// email_address_text: emailAddressText, +// stop_ignoring_voter_contact: true, +// other_voter_we_vote_id: otherVoterWeVoteId, +// }); +// }, +// +// voterContactListAugmentWithLocation (augmentWithLocation = false) { +// Dispatcher.loadEndpoint('voterContactListSave', { +// augment_voter_contact_emails_with_location: augmentWithLocation, +// }); +// }, +// +// voterContactListAugmentWithWeVoteData (augmentWithWeVoteData = false) { +// Dispatcher.loadEndpoint('voterContactListSave', { +// augment_voter_contact_emails_with_we_vote_data: augmentWithWeVoteData, +// }); +// }, +// +// voterContactListDelete (deleteAllVoterContactEmails = false) { +// Dispatcher.loadEndpoint('voterContactListSave', { +// delete_all_voter_contact_emails: deleteAllVoterContactEmails, +// }); +// }, +// +// voterContactListRetrieve () { +// Dispatcher.loadEndpoint('voterContactListRetrieve', {}); +// }, +// +// voterContactListSave (contacts, fromGooglePeopleApi = false) { +// const contactsString = JSON.stringify(contacts); +// // console.log('voterContactListSave contacts: ', contactsString); +// Dispatcher.loadEndpoint('voterContactListSave', { +// contacts: contactsString, +// from_google_people_api: fromGooglePeopleApi, +// google_api_key_type: 'ballot', +// }); +// }, +// +// voterEmailAddressRetrieve () { +// Dispatcher.loadEndpoint('voterEmailAddressRetrieve', {}); +// }, +// +// voterEmailAddressSave (voterEmailAddress, send_link_to_sign_in = false) { +// Dispatcher.loadEndpoint('voterEmailAddressSave', { +// text_for_email_address: voterEmailAddress, +// send_link_to_sign_in, +// make_primary_email: true, +// is_cordova: isCordova(), +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterEmailAddressSignIn (emailSecretKey) { +// Dispatcher.loadEndpoint('voterEmailAddressSignIn', { +// email_secret_key: emailSecretKey, +// }); +// }, +// +// voterEmailAddressSignInConfirm (emailSecretKey) { +// Dispatcher.loadEndpoint('voterEmailAddressSignIn', { +// email_secret_key: emailSecretKey, +// yes_please_merge_accounts: true, +// }); +// }, +// +// voterEmailAddressVerify (emailSecretKey, firstName = '', lastName = '', fullName = '') { +// Dispatcher.loadEndpoint('voterEmailAddressVerify', { +// email_secret_key: emailSecretKey, +// first_name: firstName, +// first_name_changed: firstName && firstName !== '', +// last_name: lastName, +// last_name_changed: lastName && lastName !== '', +// full_name: fullName, +// full_name_changed: fullName && fullName !== '', +// name_save_only_if_no_existing_names: true, +// }); +// }, +// +// voterEmailQueuedToSave (voterEmail) { +// Dispatcher.dispatch({ type: 'voterEmailQueuedToSave', payload: voterEmail }); +// }, +// +// voterExternalIdSave (externalVoterId, membershipOrganizationWeVoteId) { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// external_voter_id: externalVoterId, +// membership_organization_we_vote_id: membershipOrganizationWeVoteId, +// }); +// }, +// +// voterFacebookSaveToCurrentAccount () { +// Dispatcher.loadEndpoint('voterFacebookSaveToCurrentAccount', { +// }); +// }, +// +// voterFirstNameQueuedToSave (firstName) { +// Dispatcher.dispatch({ type: 'voterFirstNameQueuedToSave', payload: firstName }); +// }, +// +// // Tell the server to only save this name if a name does not currently exist +// voterFullNameSoftSave (firstName, lastName, fullName = '') { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// first_name: firstName, +// first_name_changed: firstName && firstName !== '', +// last_name: lastName, +// last_name_changed: lastName && lastName !== '', +// full_name: fullName, +// full_name_changed: fullName && fullName !== '', +// name_save_only_if_no_existing_names: true, +// }); +// }, +// +// voterLastNameQueuedToSave (lastName) { +// Dispatcher.dispatch({ type: 'voterLastNameQueuedToSave', payload: lastName }); +// }, +// +// voterMergeTwoAccountsByEmailKey (emailSecretKey, doNotMergeIfCurrentlySignedIn = false) { +// Dispatcher.loadEndpoint('voterMergeTwoAccounts', +// { +// do_not_merge_if_currently_signed_in: doNotMergeIfCurrentlySignedIn, +// email_secret_key: emailSecretKey, +// facebook_secret_key: '', +// incoming_voter_device_id: '', +// invitation_secret_key: '', +// twitter_secret_key: '', +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterMergeTwoAccountsByFacebookKey (facebookSecretKey) { +// // console.log("VoterActions, voterMergeTwoAccountsByFacebookKey"); +// Dispatcher.loadEndpoint('voterMergeTwoAccounts', +// { +// email_secret_key: '', +// facebook_secret_key: facebookSecretKey, +// incoming_voter_device_id: '', +// invitation_secret_key: '', +// twitter_secret_key: '', +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterMergeTwoAccountsByInvitationKey (invitationSecretKey) { +// Dispatcher.loadEndpoint('voterMergeTwoAccounts', +// { +// email_secret_key: '', +// facebook_secret_key: '', +// incoming_voter_device_id: '', +// invitation_secret_key: invitationSecretKey, +// twitter_secret_key: '', +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterMergeTwoAccountsByJumpProcess (incomingVoterDeviceId) { +// // TODO DALE 2018-01-10 voterMergeTwoAccounts doesn't support incomingVoterDeviceId yet +// Dispatcher.loadEndpoint('voterMergeTwoAccounts', +// { +// email_secret_key: '', +// facebook_secret_key: '', +// incoming_voter_device_id: incomingVoterDeviceId, +// invitation_secret_key: '', +// twitter_secret_key: '', +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterMergeTwoAccountsByTwitterKey (twitterSecretKey) { +// Dispatcher.loadEndpoint('voterMergeTwoAccounts', +// { +// email_secret_key: '', +// facebook_secret_key: '', +// incoming_voter_device_id: '', +// invitation_secret_key: '', +// twitter_secret_key: twitterSecretKey, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterNameSave (firstName, lastName) { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// first_name: firstName, +// first_name_changed: true, +// last_name: lastName, +// last_name_changed: true, +// }); +// }, +// +// voterPhotoDelete () { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// voter_photo_from_file_reader: '', +// voter_photo_changed: true, +// }); +// }, +// +// voterPhotoQueuedToSave (voterPhotoFromFileReader) { +// Dispatcher.dispatch({ type: 'voterPhotoQueuedToSave', payload: voterPhotoFromFileReader }); +// }, +// +// voterPhotoSave (voterPhotoQueuedToSave = '', voterPhotoQueuedToSaveSet = false, profileImageTypeCurrentlyActive = '') { +// // console.log('VoterActions voterPhotoSave'); +// const profileImageTypeCurrentlyActiveSet = arrayContains(profileImageTypeCurrentlyActive, ['FACEBOOK', 'TWITTER', 'UPLOADED']); +// Dispatcher.loadEndpoint('voterUpdate', +// { +// profile_image_type_currently_active: profileImageTypeCurrentlyActive, +// profile_image_type_currently_active_changed: profileImageTypeCurrentlyActiveSet, +// voter_photo_from_file_reader: voterPhotoQueuedToSave, +// voter_photo_changed: voterPhotoQueuedToSaveSet, +// }); +// }, +// +// voterPhotoTooBigReset () { +// Dispatcher.dispatch({ type: 'voterPhotoTooBigReset', payload: true }); +// }, +// +// voterRetrieve (mergeFromVoterWeVoteId = '', mergeToVoterWeVoteId = '') { +// // console.log('VoterActions, voterRetrieve mergeFromVoterWeVoteId:', mergeFromVoterWeVoteId, ', mergeToVoterWeVoteId:', mergeToVoterWeVoteId); +// if (mergeFromVoterWeVoteId && mergeToVoterWeVoteId) { +// Dispatcher.loadEndpoint('voterRetrieve', { +// merge_from_voter_we_vote_id: mergeFromVoterWeVoteId, +// merge_to_voter_we_vote_id: mergeToVoterWeVoteId, +// }); +// } else { +// Dispatcher.loadEndpoint('voterRetrieve'); +// } +// }, +// +// voterSignOut (signOutAllDevices = false) { +// Dispatcher.loadEndpoint('voterSignOut', { +// sign_out_all_devices: signOutAllDevices, +// }); +// }, +// +// voterSMSPhoneNumberRetrieve () { +// Dispatcher.loadEndpoint('voterSMSPhoneNumberRetrieve', {}); +// }, +// +// voterSMSPhoneNumberSave (smsPhoneNumber) { +// Dispatcher.loadEndpoint('voterSMSPhoneNumberSave', { +// sms_phone_number: smsPhoneNumber, +// make_primary_sms_phone_number: true, +// hostname: AppObservableStore.getHostname(), +// }); +// }, +// +// voterSplitIntoTwoAccounts () { +// Dispatcher.loadEndpoint('voterSplitIntoTwoAccounts', +// { +// split_off_twitter: true, +// }); +// }, +// +// voterTwitterSaveToCurrentAccount () { +// Dispatcher.loadEndpoint('voterTwitterSaveToCurrentAccount', { +// }); +// }, +// +// voterUpdateInterfaceStatusFlags (flagIntegerToSet) { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// flag_integer_to_set: flagIntegerToSet, +// }); +// }, +// +// voterUpdateNotificationSettingsFlags (flagIntegerToSet, flagIntegerToUnset = '') { +// Dispatcher.loadEndpoint('voterUpdate', +// { +// notification_flag_integer_to_set: flagIntegerToSet, +// notification_flag_integer_to_unset: flagIntegerToUnset, +// }); +// }, +// +// voterNotificationSettingsUpdateFromSecretKey (emailSubscriptionSecretKey = '', smsSubscriptionSecretKey = '', flagIntegerToSet = 0, flagIntegerToSetChanged = false, flagIntegerToUnset = 0, flagIntegerToUnsetChanged = false) { +// Dispatcher.loadEndpoint('voterNotificationSettingsUpdate', +// { +// email_subscription_secret_key: emailSubscriptionSecretKey, +// sms_subscription_secret_key: smsSubscriptionSecretKey, +// notification_flag_integer_to_set: flagIntegerToSet, +// notification_flag_integer_to_set_changed: flagIntegerToSetChanged, +// notification_flag_integer_to_unset: flagIntegerToUnset, +// notification_flag_integer_to_unset_changed: flagIntegerToUnsetChanged, +// }); +// }, +// +// voterVerifySecretCode (secretCode, codeSentToSMSPhoneNumber) { +// // console.log('VoterActions, voterVerifySecretCode codeSentToSMSPhoneNumber:', codeSentToSMSPhoneNumber); +// Dispatcher.loadEndpoint('voterVerifySecretCode', { +// secret_code: secretCode, +// code_sent_to_sms_phone_number: codeSentToSMSPhoneNumber, +// }); +// }, +// +// voterAppleSignInSave (email, givenName, middleName, familyName, user, identityToken) { +// // eslint-disable-next-line camelcase +// const { device: { platform: apple_platform, version: apple_os_version, model: apple_model } } = window; +// Dispatcher.loadEndpoint('appleSignInSave', { +// email, +// first_name: givenName, +// middle_name: middleName, +// last_name: familyName, +// user_code: user, +// apple_platform, +// apple_os_version, +// apple_model, +// identity_token: identityToken, +// }); +// }, +// +// deviceStoreFirebaseCloudMessagingToken (firebaseFCMToken) { +// Dispatcher.loadEndpoint('deviceStoreFirebaseCloudMessagingToken', { +// firebase_fcm_token: firebaseFCMToken, +// }); +// }, +// }; diff --git a/src/js/actions/VoterSessionActions.js b/src/js/actions/VoterSessionActions.js index 10bfe10..871326a 100644 --- a/src/js/actions/VoterSessionActions.js +++ b/src/js/actions/VoterSessionActions.js @@ -1,41 +1,41 @@ -import Dispatcher from '../common/dispatcher/Dispatcher'; -// eslint-disable-next-line import/no-cycle -import AppObservableStore from '../stores/AppObservableStore'; -import Cookies from '../common/utils/js-cookie/Cookies'; -import stringContains from '../common/utils/stringContains'; - -export default { - voterSignOut (signOutAllDevices = false) { - AppObservableStore.setShowSignInModal(false); - AppObservableStore.unsetStoreSignInStartFullUrl(); - Dispatcher.loadEndpoint('voterSignOut', { sign_out_all_devices: signOutAllDevices }); - const names = [ - 'voter_device_id', - 'ballot_has_been_visited', - 'location_guess_closed', - 'number_of_ballot_choices_made', - 'number_of_topic_choices_made', - 'show_full_navigation', - 'sign_in_opened_from_issue_follow', - 'sign_in_start_full_url', - ]; - for (let i = 0; i < names.length; i++) { - const name = names[i]; - Cookies.remove(name); - Cookies.remove(name, { path: '/' }); - Cookies.remove(name, { path: '/', domain: 'wevote.us' }); - } - }, - - setVoterDeviceIdCookie (id) { - let { hostname } = window.location; - hostname = hostname || ''; - console.log('VoterSessionActions setVoterDeviceIdCookie hostname:', hostname); - if (hostname && stringContains('wevote.us', hostname)) { - // If hanging off WeVote subdomain, store the cookie with top level domain - Cookies.set('voter_device_id', id, { expires: 10000, path: '/', domain: 'wevote.us' }); - } else { - Cookies.set('voter_device_id', id, { expires: 10000, path: '/' }); - } - }, -}; +// import Dispatcher from '../common/dispatcher/Dispatcher'; +// // eslint-disable-next-line import/no-cycle +// import AppObservableStore from '../stores/AppObservableStore'; +// import Cookies from '../common/utils/js-cookie/Cookies'; +// import stringContains from '../common/utils/stringContains'; +// +// export default { +// voterSignOut (signOutAllDevices = false) { +// AppObservableStore.setShowSignInModal(false); +// AppObservableStore.unsetStoreSignInStartFullUrl(); +// Dispatcher.loadEndpoint('voterSignOut', { sign_out_all_devices: signOutAllDevices }); +// const names = [ +// 'voter_device_id', +// 'ballot_has_been_visited', +// 'location_guess_closed', +// 'number_of_ballot_choices_made', +// 'number_of_topic_choices_made', +// 'show_full_navigation', +// 'sign_in_opened_from_issue_follow', +// 'sign_in_start_full_url', +// ]; +// for (let i = 0; i < names.length; i++) { +// const name = names[i]; +// Cookies.remove(name); +// Cookies.remove(name, { path: '/' }); +// Cookies.remove(name, { path: '/', domain: 'wevote.us' }); +// } +// }, +// +// setVoterDeviceIdCookie (id) { +// let { hostname } = window.location; +// hostname = hostname || ''; +// console.log('VoterSessionActions setVoterDeviceIdCookie hostname:', hostname); +// if (hostname && stringContains('wevote.us', hostname)) { +// // If hanging off WeVote subdomain, store the cookie with top level domain +// Cookies.set('voter_device_id', id, { expires: 10000, path: '/', domain: 'wevote.us' }); +// } else { +// Cookies.set('voter_device_id', id, { expires: 10000, path: '/' }); +// } +// }, +// }; diff --git a/src/js/components/Navigation/HeaderBar.jsx b/src/js/components/Navigation/HeaderBar.jsx index 2b34d0f..f07e310 100644 --- a/src/js/components/Navigation/HeaderBar.jsx +++ b/src/js/components/Navigation/HeaderBar.jsx @@ -9,6 +9,7 @@ import { hasIPhoneNotch } from '../../common/utils/cordovaUtils'; import { normalizedHrefPage } from '../../common/utils/hrefUtils'; import { renderLog } from '../../common/utils/logging'; import { useConnectAppContext } from '../../contexts/ConnectAppContext'; +import { clearSignedInGlobals } from '../../contexts/contextFunctions'; import { useLogoutMutation } from '../../react-query/mutations'; import weConnectQueryFn, { METHOD } from '../../react-query/WeConnectQuery'; import { displayTopMenuShadow } from '../../utils/applicationUtils'; @@ -20,7 +21,7 @@ import HeaderBarLogo from './HeaderBarLogo'; const HeaderBar = ({ hideTabs }) => { renderLog('HeaderBar'); const navigate = useNavigate(); - const { setAppContextValue, getAppContextValue } = useConnectAppContext(); + const { getAppContextValue, setAppContextValue } = useConnectAppContext(); const { mutate: mutateLogout } = useLogoutMutation(); const [scrolledDown] = useState(false); @@ -39,7 +40,7 @@ const HeaderBar = ({ hideTabs }) => { const logoutApi = async () => { const data = await weConnectQueryFn('logout', {}, METHOD.POST); console.log(`/logout response in HeaderBar -- status: '${'status'}', data: ${JSON.stringify(data)}`); - setAppContextValue('isAuthenticated', false); + clearSignedInGlobals(setAppContextValue); navigate('/login'); mutateLogout(); }; diff --git a/src/js/components/Person/AddPersonDrawerMainContent.jsx b/src/js/components/Person/AddPersonDrawerMainContent.jsx index 6511806..e691bc9 100644 --- a/src/js/components/Person/AddPersonDrawerMainContent.jsx +++ b/src/js/components/Person/AddPersonDrawerMainContent.jsx @@ -94,11 +94,11 @@ const AddPersonDrawerMainContent = () => { }; mutate(makeRequestParams(plainParams, {})); // Remove this person from the All People less Adds list (since they were added to the team) - const updatedRemainingPeopleToAdd = remainingPeopleToAdd.filter((person) => person.id !== person.id); + const updatedRemainingPeopleToAdd = remainingPeopleToAdd.filter((p) => p.id !== person.id); setRemainingPeopleToAdd(updatedRemainingPeopleToAdd); if (searchResultsList && searchResultsList.length) { // also remove them from the searchResultsList if it exists - const updatedSearchResultsList = searchResultsList.filter((person) => person.id !== person.id); + const updatedSearchResultsList = searchResultsList.filter((p) => p.id !== person.id); setSearchResultsList(updatedSearchResultsList); setMatchingCounter(updatedSearchResultsList); } @@ -157,6 +157,7 @@ const AddPersonDrawerMainContentWrapper = styled('div')` const AddPersonWrapper = styled('div')` margin-top: 32px; `; + const MatchingPerson = styled('div')` margin: 10px 0 0 10px; font-style: italic; diff --git a/src/js/components/Person/EditPersonForm.jsx b/src/js/components/Person/EditPersonForm.jsx index 9cad859..12892f4 100644 --- a/src/js/components/Person/EditPersonForm.jsx +++ b/src/js/components/Person/EditPersonForm.jsx @@ -42,7 +42,6 @@ const EditPersonForm = ({ classes }) => { // console.log('savePerson data:', JSON.stringify(activePerson)); const data = {}; - // for (const key in activePerson) { Object.keys(activePerson).forEach((key) => { const initialValue = initialPerson[key] || ''; const activeValue = activePerson[key] || ''; @@ -55,7 +54,6 @@ const EditPersonForm = ({ classes }) => { }; mutate(makeRequestParams(plainParams, data)); - // personSave(makeRequestParams(plainParams, data)); setSaveButtonActive(false); }; diff --git a/src/js/components/PrivateRoute.jsx b/src/js/components/PrivateRoute.jsx index fea29f6..1caaeaf 100644 --- a/src/js/components/PrivateRoute.jsx +++ b/src/js/components/PrivateRoute.jsx @@ -6,7 +6,7 @@ import { METHOD, useFetchData } from '../react-query/WeConnectQuery'; const PrivateRoute = () => { const location = useLocation(); - const { getAppContextValue } = useConnectAppContext(); + const { getAppContextValue, setAppContextValue } = useConnectAppContext(); const [isAuthenticated, setIsAuthenticated] = useState(null); @@ -15,6 +15,7 @@ const PrivateRoute = () => { if (isSuccessAuth) { console.log('useFetchData in PrivateRoute useEffect dataAuth good:', dataAuth, isSuccessAuth); setIsAuthenticated(dataAuth.isAuthenticated); + setAppContextValue('loggedInPersonIsAdmin', dataAuth.loggedInPersonIsAdmin); authLog('========= PrivateRoute =========== INNER isAuthenticated: ', dataAuth.isAuthenticated); } }, [dataAuth, isSuccessAuth]); diff --git a/src/js/contexts/ConnectAppContext.jsx b/src/js/contexts/ConnectAppContext.jsx index 5d1b700..6916d79 100644 --- a/src/js/contexts/ConnectAppContext.jsx +++ b/src/js/contexts/ConnectAppContext.jsx @@ -105,9 +105,10 @@ export const ConnectAppContextProvider = ({ children }) => { if (isSuccessAuth) { console.log('useFetchData in ConnectAppContext useEffect dataAuth good:', dataAuth, isSuccessAuth, isFetchingAuth); const { isAuthenticated } = dataAuth; - setAppContextValue('isAuthenticated', isAuthenticated); - setAppContextValue('authenticatedPersonId', dataAuth.personId); setAppContextValue('authenticatedPerson', dataAuth.person); + setAppContextValue('authenticatedPersonId', dataAuth.personId); + setAppContextValue('isAuthenticated', isAuthenticated); + setAppContextValue('loggedInPersonIsAdmin', dataAuth.loggedInPersonIsAdmin); console.log('=============== ConnectAppContextProvider ======= isAuthenticated: ', isAuthenticated, ' ==========='); } diff --git a/src/js/contexts/contextFunctions.jsx b/src/js/contexts/contextFunctions.jsx new file mode 100644 index 0000000..3a0b421 --- /dev/null +++ b/src/js/contexts/contextFunctions.jsx @@ -0,0 +1,8 @@ + +export const clearSignedInGlobals = (setAppContextValue) => { + setAppContextValue('authenticatedPerson', undefined); + setAppContextValue('authenticatedPersonId', -1); + setAppContextValue('isAuthenticated', false); + setAppContextValue('loggedInPersonIsAdmin', false); + setAppContextValue('personIsSignedIn', false); +}; diff --git a/src/js/pages/Login.jsx b/src/js/pages/Login.jsx index a147acf..a10910e 100644 --- a/src/js/pages/Login.jsx +++ b/src/js/pages/Login.jsx @@ -1,5 +1,6 @@ import { Button, TextField } from '@mui/material'; import { withStyles } from '@mui/styles'; +import { useQueryClient } from '@tanstack/react-query'; import PropTypes from 'prop-types'; import React, { useEffect, useRef, useState } from 'react'; import { Helmet } from 'react-helmet-async'; @@ -12,18 +13,19 @@ import { PageContentContainer } from '../components/Style/pageLayoutStyles'; import VerifySecretCodeModal from '../components/VerifySecretCodeModal'; import webAppConfig from '../config'; import { useConnectAppContext } from '../contexts/ConnectAppContext'; +import { clearSignedInGlobals } from '../contexts/contextFunctions'; import { getFullNamePreferredPerson } from '../models/PersonModel'; -import { useGetAuthMutation, useLogoutMutation } from '../react-query/mutations'; +import { useLogoutMutation } from '../react-query/mutations'; import weConnectQueryFn, { METHOD, useFetchData } from '../react-query/WeConnectQuery'; import ReactQuerySaveReadTest from '../test/ReactQuerySaveReadTest'; const Login = ({ classes }) => { - renderLog('Login'); // Set LOG_RENDER_EVENTS to log all renders + renderLog('Login'); const navigate = useNavigate(); + const queryClient = useQueryClient(); const { setAppContextValue } = useConnectAppContext(); const { mutate: mutateLogout } = useLogoutMutation(); - const { mutate: mutateAuth } = useGetAuthMutation(); const firstNameFldRef = useRef(''); const lastNameFldRef = useRef(''); @@ -35,13 +37,12 @@ const Login = ({ classes }) => { const passwordFldRef = useRef(''); const confirmPasswordFldRef = useRef(''); - const [showCreateStuff, setShowCreateStuff] = useState(false); - const [warningLine, setWarningLine] = useState(''); - const [successLine, setSuccessLine] = useState(''); const [authPerson, setAuthPerson] = useState({}); - // eslint-disable-next-line no-unused-vars - const [isAuth, setIsAuth] = useState(false); const [openVerifyModalDialog, setOpenVerifyModalDialog] = useState(false); + const [returnFromLogin, setReturnFromLogin] = useState(false); + const [showCreateStuff, setShowCreateStuff] = useState(false); + const [successLine, setSuccessLine] = useState(''); + const [warningLine, setWarningLine] = useState(''); const { data: dataAuth, isSuccess: isSuccessAuth, isFetching: isFetchingAuth } = useFetchData(['get-auth'], {}, METHOD.POST); useEffect(() => { @@ -49,16 +50,16 @@ const Login = ({ classes }) => { console.log('useFetchData in Login useEffect dataAuth good:', dataAuth, isSuccessAuth, isFetchingAuth); const { isAuthenticated } = dataAuth; - setIsAuth(isAuthenticated); const authenticatedPerson = dataAuth.person; setAuthPerson(authenticatedPerson); const success = isAuthenticated && authenticatedPerson ? `Signed in as ${getFullNamePreferredPerson(authenticatedPerson)}` : 'Please sign in'; setSuccessLine(success); - // if (isAuthenticated) { - // setTimeout(() => { - // navigate('/tasks'); - // }, 2000); - // } + setAppContextValue('loggedInPersonIsAdmin', dataAuth.loggedInPersonIsAdmin); + if (isAuthenticated && returnFromLogin) { + setTimeout(() => { + navigate('/tasks'); + }, 2000); + } } }, [dataAuth, isSuccessAuth]); @@ -79,10 +80,11 @@ const Login = ({ classes }) => { setSuccessLine(`Cheers person #${data.personId}! You are signed in!`); setAppContextValue('isAuthenticated', true); setAppContextValue('authenticatedPersonId', data.personId); + setReturnFromLogin(true); if (!data.emailVerified) { setOpenVerifyModalDialog(true); } - mutateAuth(); // to propagate the invalidation to HeaderBar (might be a better way to do this) + await queryClient.invalidateQueries('get-auth'); } else { setWarningLine(data.error.msg); setSuccessLine(''); @@ -152,8 +154,8 @@ const Login = ({ classes }) => { } }; - const signOutPressed = () => { - setAppContextValue('personIsSignedIn', false); + const useSignOutPressed = () => { + clearSignedInGlobals(setAppContextValue); logoutApi().then(); }; @@ -303,7 +305,7 @@ const Login = ({ classes }) => { classes={{ root: classes.buttonDesktop }} color="primary" variant="contained" - onClick={signOutPressed} + onClick={useSignOutPressed} > Sign Out diff --git a/src/js/pages/SystemSettings/PermissionsAdministration.jsx b/src/js/pages/SystemSettings/PermissionsAdministration.jsx new file mode 100644 index 0000000..dfe484d --- /dev/null +++ b/src/js/pages/SystemSettings/PermissionsAdministration.jsx @@ -0,0 +1,347 @@ +import { Button, Checkbox, TextField } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import PropTypes from 'prop-types'; +import React, { useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { renderLog } from '../../common/utils/logging'; +import { useConnectAppContext } from '../../contexts/ConnectAppContext'; +import { getFullNamePreferredPerson } from '../../models/PersonModel'; +import makeRequestParams from '../../react-query/makeRequestParams'; +import { usePersonSaveMutation } from '../../react-query/mutations'; + +/* global $ */ + + +const PermissionsAdministration = ({ classes }) => { + renderLog('PermissionsAdministration'); + + const { mutate } = usePersonSaveMutation(); + const { getAppContextValue, apiDataCache: { allPeopleCache } } = useConnectAppContext(); + const [peopleWorkingArray, setPeopleWorkingArray] = useState(); // Object.values(allPeopleCacheCopy1)); + const [updateCount, setUpdateCount] = useState(0); + const [isSignedInAdmin, setIsSignedInAdmin] = useState(getAppContextValue('loggedInPersonIsAdmin')); + const [errorText, setErrorText] = useState(getAppContextValue('loggedInPersonIsAdmin') ? '' : + 'These checkmarks are read-only since you do not have Admin privileges'); + + const searchByNameRef = useRef(''); + const searchByEmailRef = useRef(''); + + useEffect(() => { + const isLoggedInAdmin = getAppContextValue('loggedInPersonIsAdmin'); + if (isLoggedInAdmin !== null) { + setIsSignedInAdmin(getAppContextValue('loggedInPersonIsAdmin')); + if (isLoggedInAdmin) { + setErrorText(''); + } + console.log(' useEffect(() => getAppContextValue(\'loggedInPersonIsAdmin\') updated to: ', isLoggedInAdmin); + } else { + console.log(' useEffect(() body skipped since \'loggedInPersonIsAdmin\': ', isLoggedInAdmin); + } + }, [isSignedInAdmin]); + + useEffect(() => { + const allPeopleCacheCopy2 = JSON.parse(JSON.stringify(allPeopleCache)); + setPeopleWorkingArray(Object.values(allPeopleCacheCopy2)); + }, [allPeopleCache]); + + const adminFldRef = useRef(''); + const hiringFldRef = useRef(''); + const leadFldRef = useRef(''); + const internFldRef = useRef(''); + const activeFldRef = useRef(''); + const leaveFldRef = useRef(''); + const resignedFldRef = useRef(''); + + const SET = { + ENABLE: true, + DISABLE: false, + }; + + const setButtonState = (set, personId) => { + const saveButton = $(`#person-save-${personId}`); + const cancelButton = $(`#person-cancel-${personId}`); + if (set === SET.ENABLE) { + saveButton.removeAttr('disabled').css('background-color', 'palegreen'); + cancelButton.removeAttr('disabled').css('background-color', 'palegreen'); + } else { + saveButton.attr('disabled', 'disabled').css('background-color', 'inherit'); + cancelButton.attr('disabled', 'disabled').css('background-color', 'inherit'); + } + }; + + const cancelClicked = (event) => { + const pieces = event.target.id.split('-'); + const personId = parseInt(pieces[2]); + // TODO change the data! + const activePerson = peopleWorkingArray.find((p) => p.id === personId); + const personCached = Object.values(allPeopleCache).find((p) => p.id === personId); + Object.assign(activePerson, personCached); + setButtonState(SET.DISABLE, personId); + setUpdateCount(updateCount + 1); // setting array of arrays does not cause a re-render, due to nesting? + }; + + const saveClicked = (event) => { + const personId = parseInt(event.target.id.split('-')[2]); + const activePerson = peopleWorkingArray.find((p) => p.id === personId); + const personCached = Object.values(allPeopleCache).find((p) => p.id === personId); + + const data = {}; + Object.keys(activePerson).forEach((key) => { + const initialValue = personCached[key]; // || ''; This "||" doesn't work for booleans since it forces a 'false' to become '' + const activeValue = activePerson[key]; // || ''; and then doesn't send the 'false' to the server for the data update. + if (initialValue !== activeValue) { + data[key] = activeValue; + } + }); + const plainParams = { + personId: activePerson.id, + }; + + mutate(makeRequestParams(plainParams, data)); + console.log('Saved person: ', activePerson.id); + setTimeout(() => { + setButtonState(SET.DISABLE, personId); + setUpdateCount(updateCount + 1); // setting array of arrays does not cause a re-render, due to nesting? + }, 1500); + }; + + const onClickCheckbox = (event) => { + console.log(event); + // eslint-disable-next-line no-unused-vars + if (isSignedInAdmin) { + const pieces = event.target.id.split('-'); + const personId = parseInt(pieces[2]); + const person = peopleWorkingArray.find((p) => p.id === personId); + switch (pieces[1]) { + case 'admin': + person.isAdmin = event.target.checked; + break; + case 'hiring': + person.isHiringManager = event.target.checked; + break; + case 'lead': + person.isTeamLead = event.target.checked; + break; + case 'intern': + person.isIntern = event.target.checked; + break; + case 'active': + person.statusActive = event.target.checked; + break; + case 'leave': + person.statusOnLeave = event.target.checked; + break; + case 'resigned': + person.statusResigned = event.target.checked; + break; + default: + console.log('ERROR onClickCheckbox received invalid target id: ', event.target.id); + return; + } + setButtonState(SET.ENABLE, personId); + setUpdateCount(updateCount + 1); // setting array of arrays does not cause a re-render, due to nesting? + setPeopleWorkingArray(peopleWorkingArray); + } + }; + + const searchFunction = () => { + // As the list of staff persons grows, searching through the allPeopleCache, and even maintaining an allPeopleCache will require too much memory and bandwidth + // Eventually we should send search queries to the server, and get just the people we are interested in + // Probably there should be search limiter checkboxes like ... only get Active staff by default. + // I don't want to build this out now, in case the UI team redesigns or eliminates this page + }; + + + return ( + + + + + Search is not yet implemented + + {errorText} + + + + + + + + + + + + + + + + + + {peopleWorkingArray?.map((person) => ( + + + + + + + + + + + + + + ))} + +
NameEmailAdminHiring ManagerLeadInternActiveLeaveResigned  
{getFullNamePreferredPerson(person)}{person.emailPersonal} + + + + + + + + + + + + + + + {isSignedInAdmin && } + + {isSignedInAdmin && } +
+ +
+ ); +}; +PermissionsAdministration.propTypes = { + classes: PropTypes.object.isRequired, +}; + +const styles = () => ({ + checkboxDoneRoot: { + marginLeft: '-10px', + paddingTop: 0, + paddingBottom: 0, + }, + checkboxRoot: { + paddingTop: 0, + paddingLeft: '9px', + paddingBottom: 0, + }, + checkboxLabel: { + marginLeft: '-6px', + marginTop: 2, + }, +}); + +const ErrorText = styled('div')` + width: fit-content; + font-style: italic; + background-color: yellow; + padding: 2px; + margin-top: 25px; +`; + +const PermissionsWrapper = styled('div')` + margin-left: 15px; +`; + +const Tr = styled.tr` + &:nth-child(even) { + border-bottom: 1px solid lightblue; + } +`; + +const Th = styled.th` + padding: 10px 10px 10px 0; + min-width: ${(props) => (props.$cellwidth ? `${props.$cellwidth}px;` : ';')}; +`; + +const Td = styled.td` + text-align: center +`; + +const SearchBarWrapper = styled('div')` + margin-bottom: 16px; + display: flex; +`; + +const MatchingPerson = styled('div')` + margin: 10px 0 0 10px; + font-style: italic; +`; + +export default withStyles(styles)(PermissionsAdministration); diff --git a/src/js/pages/SystemSettings/SystemSettings.jsx b/src/js/pages/SystemSettings/SystemSettings.jsx index 0fb8ae7..970d376 100644 --- a/src/js/pages/SystemSettings/SystemSettings.jsx +++ b/src/js/pages/SystemSettings/SystemSettings.jsx @@ -12,12 +12,11 @@ import { SpanWithLinkStyle } from '../../components/Style/linkStyles'; import { PageContentContainer } from '../../components/Style/pageLayoutStyles'; import webAppConfig from '../../config'; import { useConnectAppContext, useConnectDispatch } from '../../contexts/ConnectAppContext'; -import { METHOD, useFetchData } from '../../react-query/WeConnectQuery'; -import { captureQuestionnaireListRetrieveData } from '../../models/QuestionnaireModel'; import capturePersonListRetrieveData from '../../models/capturePersonListRetrieveData'; -import { - captureTaskDefinitionListRetrieveData, captureTaskGroupListRetrieveData, captureTaskStatusListRetrieveData, -} from '../../models/TaskModel'; +import { captureQuestionnaireListRetrieveData } from '../../models/QuestionnaireModel'; +import { captureTaskDefinitionListRetrieveData, captureTaskGroupListRetrieveData, captureTaskStatusListRetrieveData } from '../../models/TaskModel'; +import { METHOD, useFetchData } from '../../react-query/WeConnectQuery'; +import PermissionsAdministration from './PermissionsAdministration'; const SystemSettings = ({ classes }) => { @@ -186,6 +185,10 @@ const SystemSettings = ({ classes }) => { Add Task Grouping + Permissions Administration + {/* We could choose to hide PermissionsAdministration for those without Admin privileges, but as it exists you can't + save without admin privileges (enforced by logic on the server). */} + ); @@ -234,6 +237,7 @@ const QuestionnaireInnerWrapper = styled('div')` `; const SettingsSubtitle = styled('h2')` + margin-top: 30px; `; export default withStyles(styles)(SystemSettings); diff --git a/src/js/pages/TeamHome.jsx b/src/js/pages/TeamHome.jsx index 1083073..eb10016 100644 --- a/src/js/pages/TeamHome.jsx +++ b/src/js/pages/TeamHome.jsx @@ -34,6 +34,7 @@ const TeamHome = ({ classes }) => { // const isAddPersonDrawerOpen = document.getElementById('addPersonDrawer'); + // TODO is this even used? const personListRetrieveResults = useFetchData(['person-list-retrieve'], {}, METHOD.GET); useEffect(() => { if (personListRetrieveResults) { diff --git a/src/js/react-query/WeConnectQuery.js b/src/js/react-query/WeConnectQuery.js index 90792a1..274e687 100644 --- a/src/js/react-query/WeConnectQuery.js +++ b/src/js/react-query/WeConnectQuery.js @@ -19,12 +19,17 @@ const weConnectQueryFn = async (queryKey, params, isGet) => { // 2/12/24 temporarily replaced: httpLog(`weConnectQueryFn ${isGet ? 'GET' : 'POST'} url.href: ${url.href}`); // DO NOT REMOVE, this is the only way to see if we are hitting the API server unnecessarily console.log(`weConnectQueryFn ${isGet ? 'GET' : 'POST'} url.href: ${url.href}`); - const response = isGet ? - await axios.get(url.href, { withCredentials: true }) : - await axios.post(url.href, params, { withCredentials: true }); - // if needed: httpLog('weConnectQueryFn response.data: ', JSON.stringify(response.data)); + let response; + try { + response = isGet ? + await axios.get(url.href, { withCredentials: true }) : + await axios.post(url.href, params, { withCredentials: true }); + // if needed: httpLog('weConnectQueryFn response.data: ', JSON.stringify(response.data)); + } catch (e) { + console.error('Axios ', (isGet ? 'axios.get' : 'axios.post'), ' error: ', e); + } - return response.data; + return response?.data; }; const useFetchData = (queryKey, fetchParams, isGet) => { diff --git a/src/js/utils/safeLoadConfig.js b/src/js/utils/safeLoadConfig.js new file mode 100644 index 0000000..e68e6ed --- /dev/null +++ b/src/js/utils/safeLoadConfig.js @@ -0,0 +1,5 @@ +import webAppConfig from '../config'; + +export default function safeLoadConfig (tag) { + return webAppConfig[tag] || true; +} From e2fbf65bfe33d1e43e6654ef1d7697c8c934a501 Mon Sep 17 00:00:00 2001 From: stevepodell Date: Mon, 17 Feb 2025 10:46:32 -0800 Subject: [PATCH 2/2] Added PermissionsAdministration. isAdmin in sql controls access, and limits ability to grant further permissions. Sign in via email/password with email verification is working well. Apis are now protected from access without cookie/sessionID. ENABLE_REACT_QUERY_TOOLS becomes much more important. Commented out more code we won't be using to make searching easier. Cleared more lint warnings. --- .../components/Person/AddPersonDrawerMainContent.jsx | 9 +-------- src/js/components/Style/sharedStyles.js | 11 +++++++++++ src/js/components/Team/AddTeamDrawerMainContent.jsx | 6 ++---- .../SystemSettings/PermissionsAdministration.jsx | 11 +---------- 4 files changed, 15 insertions(+), 22 deletions(-) create mode 100644 src/js/components/Style/sharedStyles.js diff --git a/src/js/components/Person/AddPersonDrawerMainContent.jsx b/src/js/components/Person/AddPersonDrawerMainContent.jsx index e691bc9..3df7821 100644 --- a/src/js/components/Person/AddPersonDrawerMainContent.jsx +++ b/src/js/components/Person/AddPersonDrawerMainContent.jsx @@ -7,6 +7,7 @@ import { useConnectAppContext } from '../../contexts/ConnectAppContext'; import makeRequestParams from '../../react-query/makeRequestParams'; import { useAddPersonToTeamMutation } from '../../react-query/mutations'; import { SpanWithLinkStyle } from '../Style/linkStyles'; +import { SearchBarWrapper, MatchingPerson } from '../Style/sharedStyles'; import AddPersonForm from './AddPersonForm'; @@ -158,10 +159,6 @@ const AddPersonWrapper = styled('div')` margin-top: 32px; `; -const MatchingPerson = styled('div')` - margin: 10px 0 0 10px; - font-style: italic; -`; // const PersonDirectoryWrapper = styled('div')` // margin-top: 16px; @@ -181,8 +178,4 @@ const PersonSearchResultsWrapper = styled('div')` margin-top: 16px; `; -const SearchBarWrapper = styled('div')` - margin-bottom: 16px; -`; - export default AddPersonDrawerMainContent; diff --git a/src/js/components/Style/sharedStyles.js b/src/js/components/Style/sharedStyles.js new file mode 100644 index 0000000..c4fd278 --- /dev/null +++ b/src/js/components/Style/sharedStyles.js @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +export const SearchBarWrapper = styled('div')` + margin-bottom: 16px; + display: flex; +`; + +export const MatchingPerson = styled('div')` + margin: 10px 0 0 10px; + font-style: italic; +`; diff --git a/src/js/components/Team/AddTeamDrawerMainContent.jsx b/src/js/components/Team/AddTeamDrawerMainContent.jsx index 3db5228..17e952b 100644 --- a/src/js/components/Team/AddTeamDrawerMainContent.jsx +++ b/src/js/components/Team/AddTeamDrawerMainContent.jsx @@ -4,8 +4,9 @@ import React, { useState } from 'react'; import styled from 'styled-components'; import SearchBar2024 from '../../common/components/Search/SearchBar2024'; import { renderLog } from '../../common/utils/logging'; -import AddTeamForm from './AddTeamForm'; import { useConnectAppContext } from '../../contexts/ConnectAppContext'; +import { SearchBarWrapper } from '../Style/sharedStyles'; +import AddTeamForm from './AddTeamForm'; // eslint-disable-next-line no-unused-vars @@ -82,9 +83,6 @@ const AddTeamWrapper = styled('div')` margin-top: 32px; `; -const SearchBarWrapper = styled('div')` - margin-bottom: 16px; -`; const TeamItem = styled('div')` `; diff --git a/src/js/pages/SystemSettings/PermissionsAdministration.jsx b/src/js/pages/SystemSettings/PermissionsAdministration.jsx index dfe484d..8f2044b 100644 --- a/src/js/pages/SystemSettings/PermissionsAdministration.jsx +++ b/src/js/pages/SystemSettings/PermissionsAdministration.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import React, { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { renderLog } from '../../common/utils/logging'; +import { MatchingPerson, SearchBarWrapper } from '../../components/Style/sharedStyles'; import { useConnectAppContext } from '../../contexts/ConnectAppContext'; import { getFullNamePreferredPerson } from '../../models/PersonModel'; import makeRequestParams from '../../react-query/makeRequestParams'; @@ -334,14 +335,4 @@ const Td = styled.td` text-align: center `; -const SearchBarWrapper = styled('div')` - margin-bottom: 16px; - display: flex; -`; - -const MatchingPerson = styled('div')` - margin: 10px 0 0 10px; - font-style: italic; -`; - export default withStyles(styles)(PermissionsAdministration);