From 28c45882ec4e13c85b3aa33d0ba4655f395ec342 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 10:44:51 -0500 Subject: [PATCH 01/11] refactor account settings ans TOS pages --- jsapp/js/account/account.utils.ts | 39 +---- .../account/accountFieldsEditor.component.tsx | 5 +- ...oute.tsx => accountSettings.component.tsx} | 158 +++++++----------- jsapp/js/account/routes.constants.ts | 2 +- jsapp/js/tos/tosForm.component.tsx | 41 +++-- 5 files changed, 96 insertions(+), 149 deletions(-) rename jsapp/js/account/{accountSettingsRoute.tsx => accountSettings.component.tsx} (52%) diff --git a/jsapp/js/account/account.utils.ts b/jsapp/js/account/account.utils.ts index 6a242324db..83f3d238de 100644 --- a/jsapp/js/account/account.utils.ts +++ b/jsapp/js/account/account.utils.ts @@ -25,41 +25,6 @@ export function getInitialAccountFieldsValues(): AccountFieldsValues { * For given field values produces an object to use with the `/me` endpoint for * updating the `extra_details`. */ -export function getProfilePatchData(fields: AccountFieldsValues) { - // HACK: dumb down the `output` type here, so TS doesn't have a problem with - // types inside the `forEach` loop below, and the output is compatible with - // functions from `api.ts` file. - const output: {extra_details: {[key: string]: any}} = { - extra_details: getInitialAccountFieldsValues(), - }; - - // To patch correctly with recent changes to the backend, - // ensure that we send empty strings if the field is left blank. - - // We should only overwrite user metadata that the user can see. - // Fields that: - // (a) are enabled in constance - // (b) the frontend knows about - - // Make a list of user metadata fields to include in the patch - const presentMetadataFields = - // Fields enabled in constance - envStore.data - .getUserMetadataFieldNames() - // Intersected with: - .filter( - (fieldName) => - // Fields the frontend knows about - fieldName in USER_FIELD_NAMES - ); - - // Populate the patch with user form input, or empty strings. - presentMetadataFields.forEach((fieldName) => { - output.extra_details[fieldName] = fields[fieldName] || ''; - }); - - // Always include require_auth, defaults to 'false'. - output.extra_details.require_auth = fields.require_auth ? true : false; - - return output; +export function getProfilePatchData(fields: Partial) { + return {extra_details: fields}; } diff --git a/jsapp/js/account/accountFieldsEditor.component.tsx b/jsapp/js/account/accountFieldsEditor.component.tsx index b52a1cdcaf..ce8e5dea81 100644 --- a/jsapp/js/account/accountFieldsEditor.component.tsx +++ b/jsapp/js/account/accountFieldsEditor.component.tsx @@ -42,7 +42,7 @@ interface AccountFieldsEditorProps { * `displayedFields` prop) */ values: AccountFieldsValues; - onChange: (fields: AccountFieldsValues) => void; + onFieldChange: (fieldName: UserFieldName, value: UserFieldValue) => void; } /** @@ -82,8 +82,7 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { fieldName: UserFieldName, newValue: UserFieldValue ) { - const newValues = {...props.values, [fieldName]: newValue}; - props.onChange(newValues); + props.onFieldChange(fieldName, newValue); } const cleanedUrl = (value: string) => { diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettings.component.tsx similarity index 52% rename from jsapp/js/account/accountSettingsRoute.tsx rename to jsapp/js/account/accountSettings.component.tsx index 91e7b6ea69..889fda27e1 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettings.component.tsx @@ -1,13 +1,13 @@ -import React, {useEffect, useState} from 'react'; +import type React from 'react'; +import {useEffect, useState} from 'react'; import Button from 'js/components/common/button'; import InlineMessage from 'js/components/common/inlineMessage'; -import {observer} from 'mobx-react'; -import type {Form} from 'react-router-dom'; import {unstable_usePrompt as usePrompt} from 'react-router-dom'; import bem, {makeBem} from 'js/bem'; import sessionStore from 'js/stores/session'; import './accountSettings.scss'; import {notify} from 'js/utils'; +import type {AccountResponse} from '../dataInterface'; import {dataInterface} from '../dataInterface'; import AccountFieldsEditor from './accountFieldsEditor.component'; import Avatar from 'js/components/common/avatar'; @@ -21,6 +21,7 @@ import type { AccountFieldsErrors, } from './account.constants'; import {HELP_ARTICLE_ANON_SUBMISSIONS_URL} from 'js/constants'; +import {useLocalObservable} from 'mobx-react'; bem.AccountSettings = makeBem(null, 'account-settings', 'form'); bem.AccountSettings__left = makeBem(bem.AccountSettings, 'left'); @@ -28,109 +29,78 @@ bem.AccountSettings__right = makeBem(bem.AccountSettings, 'right'); bem.AccountSettings__item = makeBem(bem.FormModal, 'item'); bem.AccountSettings__actions = makeBem(bem.AccountSettings, 'actions'); -interface Form { - isPristine: boolean; - /** - * Whether we have loaded the user metadata values. Used to avoid displaying - * blank form with values coming in a moment later (in visible way). - */ - isUserDataLoaded: boolean; - fields: AccountFieldsValues; - fieldsWithErrors: { - extra_details?: AccountFieldsErrors; - }; -} - -const AccountSettings = observer(() => { - const [form, setForm] = useState
({ - isPristine: true, - isUserDataLoaded: false, - fields: getInitialAccountFieldsValues(), - fieldsWithErrors: {}, - }); +const AccountSettings = () => { + + const [isPristine, setIsPristine] = useState(true); + const [fieldErrors, setFieldErrors] = useState({}); + const [formFields, setFormFields] = useState(getInitialAccountFieldsValues()); + const [editedFields, setEditedFields] = useState>({}); + + const currentAccount = useLocalObservable(() => sessionStore.currentAccount as AccountResponse); useEffect(() => { - if ( - !sessionStore.isPending && - sessionStore.isInitialLoadComplete && - !sessionStore.isInitialRoute - ) { - sessionStore.refreshAccount(); - } - }, []); - useEffect(() => { - const currentAccount = sessionStore.currentAccount; - if ( - !sessionStore.isPending && - sessionStore.isInitialLoadComplete && - 'email' in currentAccount - ) { - setForm({ - ...form, - isUserDataLoaded: true, - fields: { - name: currentAccount.extra_details.name, - organization_type: currentAccount.extra_details.organization_type, - organization: currentAccount.extra_details.organization, - organization_website: - currentAccount.extra_details.organization_website, - sector: currentAccount.extra_details.sector, - gender: currentAccount.extra_details.gender, - bio: currentAccount.extra_details.bio, - city: currentAccount.extra_details.city, - country: currentAccount.extra_details.country, - require_auth: currentAccount.extra_details.require_auth, - twitter: currentAccount.extra_details.twitter, - linkedin: currentAccount.extra_details.linkedin, - instagram: currentAccount.extra_details.instagram, - newsletter_subscription: - currentAccount.extra_details.newsletter_subscription, - }, - fieldsWithErrors: {}, - }); - } - }, [sessionStore.isPending]); + if (!currentAccount) {return;} + + setFormFields({ + name: currentAccount.extra_details.name, + organization_type: currentAccount.extra_details.organization_type, + organization: currentAccount.extra_details.organization, + organization_website: + currentAccount.extra_details.organization_website, + sector: currentAccount.extra_details.sector, + gender: currentAccount.extra_details.gender, + bio: currentAccount.extra_details.bio, + city: currentAccount.extra_details.city, + country: currentAccount.extra_details.country, + require_auth: currentAccount.extra_details.require_auth, + twitter: currentAccount.extra_details.twitter, + linkedin: currentAccount.extra_details.linkedin, + instagram: currentAccount.extra_details.instagram, + newsletter_subscription: + currentAccount.extra_details.newsletter_subscription, + }); + + }, [currentAccount]); + usePrompt({ - when: !form.isPristine, + when: !isPristine, message: t('You have unsaved changes. Leave settings without saving?'), }); + + const onUpdateComplete = () => { + notify(t('Updated profile successfully')); + setIsPristine(true); + setFieldErrors({}); + }; + + const onUpdateFail = (errors: AccountFieldsErrors) => { + setFieldErrors(errors); + }; + const updateProfile = (e: React.FormEvent) => { e?.preventDefault?.(); // Prevent form submission page reload - const profilePatchData = getProfilePatchData(form.fields); + const patchData = getProfilePatchData(editedFields); dataInterface - .patchProfile(profilePatchData) + .patchProfile(patchData) .done(() => { onUpdateComplete(); }) .fail((...args: any) => { - onUpdateFail(args); + onUpdateFail(args[0].responseJSON); }); }; - const onAccountFieldsEditorChange = (fields: AccountFieldsValues) => { - setForm({ - ...form, - fields: fields, - isPristine: false, - }); - }; - - const onUpdateComplete = () => { - notify(t('Updated profile successfully')); - setForm({ - ...form, - isPristine: true, - fieldsWithErrors: {}, + const onFieldChange = (fieldName: string, value: string | boolean) => { + setFormFields({ + ...formFields, + [fieldName]: value, }); - }; - - const onUpdateFail = (data: any) => { - setForm({ - ...form, - isPristine: false, - fieldsWithErrors: data[0].responseJSON, + setEditedFields({ + ...editedFields, + [fieldName]: value, }); + setIsPristine(false); }; const accountName = sessionStore.currentAccount.username; @@ -143,7 +113,7 @@ const AccountSettings = observer(() => { className='account-settings-save' size={'l'} isSubmit - label={t('Save Changes') + (form.isPristine ? '' : ' *')} + label={t('Save Changes') + (isPristine ? '' : ' *')} /> @@ -152,7 +122,7 @@ const AccountSettings = observer(() => { - {sessionStore.isInitialLoadComplete && form.isUserDataLoaded && ( + {currentAccount && ( { /> )} ); -}); +}; export default AccountSettings; diff --git a/jsapp/js/account/routes.constants.ts b/jsapp/js/account/routes.constants.ts index 4a4e49b6ea..e49b3d998a 100644 --- a/jsapp/js/account/routes.constants.ts +++ b/jsapp/js/account/routes.constants.ts @@ -14,7 +14,7 @@ export const AddOnsRoute = React.lazy( () => import(/* webpackPrefetch: true */ './addOns/addOns.component') ); export const AccountSettings = React.lazy( - () => import(/* webpackPrefetch: true */ './accountSettingsRoute') + () => import(/* webpackPrefetch: true */ './accountSettings.component') ); export const DataStorage = React.lazy( () => import(/* webpackPrefetch: true */ './usage/usageTopTabs') diff --git a/jsapp/js/tos/tosForm.component.tsx b/jsapp/js/tos/tosForm.component.tsx index 931f8295bb..b32bb38314 100644 --- a/jsapp/js/tos/tosForm.component.tsx +++ b/jsapp/js/tos/tosForm.component.tsx @@ -1,10 +1,11 @@ -import React, {useState, useEffect} from 'react'; +import type React from 'react'; +import {useState, useEffect} from 'react'; import Button from 'js/components/common/button'; import envStore from 'js/envStore'; import sessionStore from 'js/stores/session'; import {fetchGet, fetchPatch, fetchPost, handleApiFail} from 'js/api'; import styles from './tosForm.module.scss'; -import type {FailResponse, PaginatedResponse} from 'js/dataInterface'; +import type {AccountResponse, FailResponse, PaginatedResponse} from 'js/dataInterface'; import LoadingSpinner from 'js/components/common/loadingSpinner'; import { getInitialAccountFieldsValues, @@ -16,6 +17,7 @@ import type { AccountFieldsErrors, } from 'js/account/account.constants'; import {currentLang, notify} from 'js/utils'; +import {useLocalObservable} from 'mobx-react'; /** A slug for the `sitewide_messages` endpoint */ const TOS_SLUG = 'terms_of_service'; @@ -51,10 +53,11 @@ export default function TOSForm() { const [announcementMessage, setAnnouncementMessage] = useState< string | undefined >(); - const [fields, setFields] = useState( + const [formFields, setFormFields] = useState( getInitialAccountFieldsValues() ); const [fieldsErrors, setFieldsErrors] = useState({}); + const [editedFields, setEditedFields] = useState>({}); const fieldsToShow = envStore.data.getUserMetadataRequiredFieldNames(); if ( @@ -63,6 +66,8 @@ export default function TOSForm() { fieldsToShow.push('newsletter_subscription'); } + const currentAccount = useLocalObservable(() => sessionStore.currentAccount as AccountResponse); + // Get TOS message from endpoint useEffect(() => { const getTOS = async () => { @@ -104,9 +109,10 @@ export default function TOSForm() { // (including the non-required ones that will be hidden, but passed to the API // so that they will not get erased). useEffect(() => { - if ('email' in sessionStore.currentAccount) { - const data = sessionStore.currentAccount; - setFields({ + if (!currentAccount) {return;} + + const data = sessionStore.currentAccount as AccountResponse; + setFormFields({ name: data.extra_details.name, organization: data.extra_details.organization, organization_website: data.extra_details.organization_website, @@ -122,12 +128,19 @@ export default function TOSForm() { instagram: data.extra_details.instagram, newsletter_subscription: data.extra_details.newsletter_subscription, }); - } - }, [sessionStore.isAuthStateKnown]); - function onAccountFieldsEditorChange(newFields: AccountFieldsValues) { - setFields(newFields); - } + }, [currentAccount]); + + const onFieldChange = (fieldName: string, value: string | boolean) => { + setFormFields({ + ...formFields, + [fieldName]: value, + }); + setEditedFields({ + ...editedFields, + [fieldName]: value, + }); + }; /** * Submitting does two things (with two consecutive API calls): @@ -146,7 +159,7 @@ export default function TOSForm() { // them. if (fieldsToShow.length > 0) { // Get data for the user endpoint - const profilePatchData = getProfilePatchData(fields); + const profilePatchData = getProfilePatchData(editedFields); try { await fetchPatch(ME_ENDPOINT, profilePatchData); @@ -217,8 +230,8 @@ export default function TOSForm() { )} From f1bd1f4b3131671eddfdc8df685f21cef45b9986 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 11:51:36 -0500 Subject: [PATCH 02/11] cleanup unused import --- jsapp/js/account/accountFieldsEditor.component.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/jsapp/js/account/accountFieldsEditor.component.tsx b/jsapp/js/account/accountFieldsEditor.component.tsx index ce8e5dea81..15e01dc7a8 100644 --- a/jsapp/js/account/accountFieldsEditor.component.tsx +++ b/jsapp/js/account/accountFieldsEditor.component.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import Checkbox from '../components/common/checkbox'; import TextBox from '../components/common/textBox'; import {addRequiredToLabel} from 'js/textUtils'; From fb3fc2ddf65ab5f1f6e1a995c8e02c47c777b64c Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 11:53:35 -0500 Subject: [PATCH 03/11] linting, cleanup and formatting --- jsapp/js/account/account.utils.ts | 2 - .../account/accountFieldsEditor.component.tsx | 223 ++++++++++-------- .../js/account/accountSettings.component.tsx | 68 +++--- jsapp/js/tos/tosForm.component.tsx | 53 +++-- 4 files changed, 190 insertions(+), 156 deletions(-) diff --git a/jsapp/js/account/account.utils.ts b/jsapp/js/account/account.utils.ts index 83f3d238de..792f0bb6cb 100644 --- a/jsapp/js/account/account.utils.ts +++ b/jsapp/js/account/account.utils.ts @@ -1,6 +1,4 @@ import type {AccountFieldsValues} from './account.constants'; -import envStore from '../envStore'; -import {USER_FIELD_NAMES} from './account.constants'; export function getInitialAccountFieldsValues(): AccountFieldsValues { return { diff --git a/jsapp/js/account/accountFieldsEditor.component.tsx b/jsapp/js/account/accountFieldsEditor.component.tsx index 15e01dc7a8..e5294024a5 100644 --- a/jsapp/js/account/accountFieldsEditor.component.tsx +++ b/jsapp/js/account/accountFieldsEditor.component.tsx @@ -96,7 +96,11 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { value = 'http://' + value; // add missing protocol } // normalize '://' and trailing slash if URL is valid - try {value = new URL(value).toString();} catch (e) {/**/} + try { + value = new URL(value).toString(); + } catch (e) { + /**/ + } return value; }; @@ -199,43 +203,46 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { {/* Full name */} {/* Comma operator evaluates left-to-right, returns rightmost operand. We increment fieldCount and ignore the result. */} - {isFieldToBeDisplayed('name') && (fieldCount++, ( -
- -
- ))} + {isFieldToBeDisplayed('name') && + (fieldCount++, + ( +
+ +
+ ))} {/* Gender */} - {isFieldToBeDisplayed('gender') && (fieldCount++, ( -
- - onAnyFieldChange('gender', value || '') - } - options={GENDER_SELECT_OPTIONS} - error={props.errors?.gender} - /> -
- ))} - + {isFieldToBeDisplayed('gender') && + (fieldCount++, + ( +
+ + onAnyFieldChange('gender', value || '') + } + options={GENDER_SELECT_OPTIONS} + error={props.errors?.gender} + /> +
+ ))} {/* Start a new row for country and city if both are present. @@ -247,79 +254,87 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { fieldCount++ &&
} {/* Country */} - {isFieldToBeDisplayed('country') && (fieldCount++, ( -
- - onAnyFieldChange('country', value || '') - } - options={envStore.data.country_choices} - error={props.errors?.country} - /> -
- ))} + {isFieldToBeDisplayed('country') && + (fieldCount++, + ( +
+ + onAnyFieldChange('country', value || '') + } + options={envStore.data.country_choices} + error={props.errors?.country} + /> +
+ ))} {/* City */} - {isFieldToBeDisplayed('city') && (fieldCount++, ( -
- -
- ))} + {isFieldToBeDisplayed('city') && + (fieldCount++, + ( +
+ +
+ ))} {/* Primary Sector */} - {isFieldToBeDisplayed('sector') && (fieldCount++, ( -
- - onAnyFieldChange('sector', value || '') - } - options={envStore.data.sector_choices} - error={props.errors?.sector} - /> -
- ))} + {isFieldToBeDisplayed('sector') && + (fieldCount++, + ( +
+ + onAnyFieldChange('sector', value || '') + } + options={envStore.data.sector_choices} + error={props.errors?.sector} + /> +
+ ))} {/* Organization Type */} - {isOrganizationTypeFieldToBeDisplayed() && (fieldCount++, ( -
- - onAnyFieldChange('organization_type', value || '') - } - options={ORGANIZATION_TYPE_SELECT_OPTIONS} - error={props.errors?.organization_type} - noMaxMenuHeight - /> -
- ))} + {isOrganizationTypeFieldToBeDisplayed() && + (fieldCount++, + ( +
+ + onAnyFieldChange('organization_type', value || '') + } + options={ORGANIZATION_TYPE_SELECT_OPTIONS} + error={props.errors?.organization_type} + noMaxMenuHeight + /> +
+ ))} {/* Start a new row for these two organization fields if both are present. diff --git a/jsapp/js/account/accountSettings.component.tsx b/jsapp/js/account/accountSettings.component.tsx index 889fda27e1..370e5a2a8d 100644 --- a/jsapp/js/account/accountSettings.component.tsx +++ b/jsapp/js/account/accountSettings.component.tsx @@ -30,36 +30,41 @@ bem.AccountSettings__item = makeBem(bem.FormModal, 'item'); bem.AccountSettings__actions = makeBem(bem.AccountSettings, 'actions'); const AccountSettings = () => { - const [isPristine, setIsPristine] = useState(true); const [fieldErrors, setFieldErrors] = useState({}); - const [formFields, setFormFields] = useState(getInitialAccountFieldsValues()); - const [editedFields, setEditedFields] = useState>({}); + const [formFields, setFormFields] = useState( + getInitialAccountFieldsValues() + ); + const [editedFields, setEditedFields] = useState< + Partial + >({}); - const currentAccount = useLocalObservable(() => sessionStore.currentAccount as AccountResponse); + const currentAccount = useLocalObservable( + () => sessionStore.currentAccount as AccountResponse + ); useEffect(() => { - if (!currentAccount) {return;} + if (!currentAccount) { + return; + } setFormFields({ - name: currentAccount.extra_details.name, - organization_type: currentAccount.extra_details.organization_type, - organization: currentAccount.extra_details.organization, - organization_website: - currentAccount.extra_details.organization_website, - sector: currentAccount.extra_details.sector, - gender: currentAccount.extra_details.gender, - bio: currentAccount.extra_details.bio, - city: currentAccount.extra_details.city, - country: currentAccount.extra_details.country, - require_auth: currentAccount.extra_details.require_auth, - twitter: currentAccount.extra_details.twitter, - linkedin: currentAccount.extra_details.linkedin, - instagram: currentAccount.extra_details.instagram, - newsletter_subscription: - currentAccount.extra_details.newsletter_subscription, + name: currentAccount.extra_details.name, + organization_type: currentAccount.extra_details.organization_type, + organization: currentAccount.extra_details.organization, + organization_website: currentAccount.extra_details.organization_website, + sector: currentAccount.extra_details.sector, + gender: currentAccount.extra_details.gender, + bio: currentAccount.extra_details.bio, + city: currentAccount.extra_details.city, + country: currentAccount.extra_details.country, + require_auth: currentAccount.extra_details.require_auth, + twitter: currentAccount.extra_details.twitter, + linkedin: currentAccount.extra_details.linkedin, + instagram: currentAccount.extra_details.instagram, + newsletter_subscription: + currentAccount.extra_details.newsletter_subscription, }); - }, [currentAccount]); usePrompt({ @@ -119,7 +124,7 @@ const AccountSettings = () => { - + {currentAccount && ( @@ -127,22 +132,29 @@ const AccountSettings = () => { - {t('You can now control whether to allow anonymous submissions in web forms for each project. Previously, this was an account-wide setting.')} + {t( + 'You can now control whether to allow anonymous submissions in web forms for each project. Previously, this was an account-wide setting.' + )}   - {t('This privacy feature is now a per-project setting. New projects will require authentication by default.')} + {t( + 'This privacy feature is now a per-project setting. New projects will require authentication by default.' + )}   {t('Learn more about these changes here.')} - )} + } className='anonymous-submission-notice' /> diff --git a/jsapp/js/tos/tosForm.component.tsx b/jsapp/js/tos/tosForm.component.tsx index b32bb38314..9acc99745e 100644 --- a/jsapp/js/tos/tosForm.component.tsx +++ b/jsapp/js/tos/tosForm.component.tsx @@ -5,7 +5,11 @@ import envStore from 'js/envStore'; import sessionStore from 'js/stores/session'; import {fetchGet, fetchPatch, fetchPost, handleApiFail} from 'js/api'; import styles from './tosForm.module.scss'; -import type {AccountResponse, FailResponse, PaginatedResponse} from 'js/dataInterface'; +import type { + AccountResponse, + FailResponse, + PaginatedResponse, +} from 'js/dataInterface'; import LoadingSpinner from 'js/components/common/loadingSpinner'; import { getInitialAccountFieldsValues, @@ -57,7 +61,9 @@ export default function TOSForm() { getInitialAccountFieldsValues() ); const [fieldsErrors, setFieldsErrors] = useState({}); - const [editedFields, setEditedFields] = useState>({}); + const [editedFields, setEditedFields] = useState< + Partial + >({}); const fieldsToShow = envStore.data.getUserMetadataRequiredFieldNames(); if ( @@ -66,7 +72,9 @@ export default function TOSForm() { fieldsToShow.push('newsletter_subscription'); } - const currentAccount = useLocalObservable(() => sessionStore.currentAccount as AccountResponse); + const currentAccount = useLocalObservable( + () => sessionStore.currentAccount as AccountResponse + ); // Get TOS message from endpoint useEffect(() => { @@ -109,26 +117,27 @@ export default function TOSForm() { // (including the non-required ones that will be hidden, but passed to the API // so that they will not get erased). useEffect(() => { - if (!currentAccount) {return;} - - const data = sessionStore.currentAccount as AccountResponse; - setFormFields({ - name: data.extra_details.name, - organization: data.extra_details.organization, - organization_website: data.extra_details.organization_website, - organization_type: data.extra_details.organization_type, - sector: data.extra_details.sector, - gender: data.extra_details.gender, - bio: data.extra_details.bio, - city: data.extra_details.city, - country: data.extra_details.country, - require_auth: data.extra_details.require_auth, - twitter: data.extra_details.twitter, - linkedin: data.extra_details.linkedin, - instagram: data.extra_details.instagram, - newsletter_subscription: data.extra_details.newsletter_subscription, - }); + if (!currentAccount) { + return; + } + const data = sessionStore.currentAccount as AccountResponse; + setFormFields({ + name: data.extra_details.name, + organization: data.extra_details.organization, + organization_website: data.extra_details.organization_website, + organization_type: data.extra_details.organization_type, + sector: data.extra_details.sector, + gender: data.extra_details.gender, + bio: data.extra_details.bio, + city: data.extra_details.city, + country: data.extra_details.country, + require_auth: data.extra_details.require_auth, + twitter: data.extra_details.twitter, + linkedin: data.extra_details.linkedin, + instagram: data.extra_details.instagram, + newsletter_subscription: data.extra_details.newsletter_subscription, + }); }, [currentAccount]); const onFieldChange = (fieldName: string, value: string | boolean) => { From bbd9a829d983d056629feaeeca3bdab1238b7edb Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 14:56:26 -0500 Subject: [PATCH 04/11] revert renaming --- .../{accountSettings.component.tsx => accountSettingsRoute.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jsapp/js/account/{accountSettings.component.tsx => accountSettingsRoute.tsx} (100%) diff --git a/jsapp/js/account/accountSettings.component.tsx b/jsapp/js/account/accountSettingsRoute.tsx similarity index 100% rename from jsapp/js/account/accountSettings.component.tsx rename to jsapp/js/account/accountSettingsRoute.tsx From e4c272d16861661987eab171b3941bb0c5bf6563 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 14:56:43 -0500 Subject: [PATCH 05/11] revert auto formatting --- .../account/accountFieldsEditor.component.tsx | 224 ++++++++---------- 1 file changed, 105 insertions(+), 119 deletions(-) diff --git a/jsapp/js/account/accountFieldsEditor.component.tsx b/jsapp/js/account/accountFieldsEditor.component.tsx index e5294024a5..ce8e5dea81 100644 --- a/jsapp/js/account/accountFieldsEditor.component.tsx +++ b/jsapp/js/account/accountFieldsEditor.component.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import Checkbox from '../components/common/checkbox'; import TextBox from '../components/common/textBox'; import {addRequiredToLabel} from 'js/textUtils'; @@ -96,11 +97,7 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { value = 'http://' + value; // add missing protocol } // normalize '://' and trailing slash if URL is valid - try { - value = new URL(value).toString(); - } catch (e) { - /**/ - } + try {value = new URL(value).toString();} catch (e) {/**/} return value; }; @@ -203,46 +200,43 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { {/* Full name */} {/* Comma operator evaluates left-to-right, returns rightmost operand. We increment fieldCount and ignore the result. */} - {isFieldToBeDisplayed('name') && - (fieldCount++, - ( -
- -
- ))} + {isFieldToBeDisplayed('name') && (fieldCount++, ( +
+ +
+ ))} {/* Gender */} - {isFieldToBeDisplayed('gender') && - (fieldCount++, - ( -
- - onAnyFieldChange('gender', value || '') - } - options={GENDER_SELECT_OPTIONS} - error={props.errors?.gender} - /> -
- ))} + {isFieldToBeDisplayed('gender') && (fieldCount++, ( +
+ + onAnyFieldChange('gender', value || '') + } + options={GENDER_SELECT_OPTIONS} + error={props.errors?.gender} + /> +
+ ))} + {/* Start a new row for country and city if both are present. @@ -254,87 +248,79 @@ export default function AccountFieldsEditor(props: AccountFieldsEditorProps) { fieldCount++ &&
} {/* Country */} - {isFieldToBeDisplayed('country') && - (fieldCount++, - ( -
- - onAnyFieldChange('country', value || '') - } - options={envStore.data.country_choices} - error={props.errors?.country} - /> -
- ))} + {isFieldToBeDisplayed('country') && (fieldCount++, ( +
+ + onAnyFieldChange('country', value || '') + } + options={envStore.data.country_choices} + error={props.errors?.country} + /> +
+ ))} {/* City */} - {isFieldToBeDisplayed('city') && - (fieldCount++, - ( -
- -
- ))} + {isFieldToBeDisplayed('city') && (fieldCount++, ( +
+ +
+ ))} {/* Primary Sector */} - {isFieldToBeDisplayed('sector') && - (fieldCount++, - ( -
- - onAnyFieldChange('sector', value || '') - } - options={envStore.data.sector_choices} - error={props.errors?.sector} - /> -
- ))} + {isFieldToBeDisplayed('sector') && (fieldCount++, ( +
+ + onAnyFieldChange('sector', value || '') + } + options={envStore.data.sector_choices} + error={props.errors?.sector} + /> +
+ ))} {/* Organization Type */} - {isOrganizationTypeFieldToBeDisplayed() && - (fieldCount++, - ( -
- - onAnyFieldChange('organization_type', value || '') - } - options={ORGANIZATION_TYPE_SELECT_OPTIONS} - error={props.errors?.organization_type} - noMaxMenuHeight - /> -
- ))} + {isOrganizationTypeFieldToBeDisplayed() && (fieldCount++, ( +
+ + onAnyFieldChange('organization_type', value || '') + } + options={ORGANIZATION_TYPE_SELECT_OPTIONS} + error={props.errors?.organization_type} + noMaxMenuHeight + /> +
+ ))} {/* Start a new row for these two organization fields if both are present. From b30c5b0f37c40bc40aac057d726aaa845f9ebe21 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 14:57:09 -0500 Subject: [PATCH 06/11] revert file renaming --- jsapp/js/account/routes.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsapp/js/account/routes.constants.ts b/jsapp/js/account/routes.constants.ts index e49b3d998a..4a4e49b6ea 100644 --- a/jsapp/js/account/routes.constants.ts +++ b/jsapp/js/account/routes.constants.ts @@ -14,7 +14,7 @@ export const AddOnsRoute = React.lazy( () => import(/* webpackPrefetch: true */ './addOns/addOns.component') ); export const AccountSettings = React.lazy( - () => import(/* webpackPrefetch: true */ './accountSettings.component') + () => import(/* webpackPrefetch: true */ './accountSettingsRoute') ); export const DataStorage = React.lazy( () => import(/* webpackPrefetch: true */ './usage/usageTopTabs') From 3982e72885eb7cdfd48a0ea24f277d43524f9df7 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 21 Nov 2024 16:29:04 -0500 Subject: [PATCH 07/11] Making sure user is logged in --- jsapp/js/account/accountSettingsRoute.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettingsRoute.tsx index 370e5a2a8d..666a1ba5c5 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettingsRoute.tsx @@ -39,8 +39,11 @@ const AccountSettings = () => { Partial >({}); - const currentAccount = useLocalObservable( - () => sessionStore.currentAccount as AccountResponse + // We're verifying that the user is logged in so we can consider the current account is from a valid logged user + const {currentAccount} = useLocalObservable( + () => { + return {currentAccount: sessionStore.isLoggedIn && (sessionStore.currentAccount as AccountResponse)}; + } ); useEffect(() => { From 980f73a83e5a81f03fcc7c94daf4ec14ba45d8b5 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Tue, 26 Nov 2024 13:12:05 -0300 Subject: [PATCH 08/11] use new useSession hook --- jsapp/js/account/accountSettingsRoute.tsx | 42 ++++++++++------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettingsRoute.tsx index 666a1ba5c5..9ce71f32c8 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettingsRoute.tsx @@ -21,7 +21,7 @@ import type { AccountFieldsErrors, } from './account.constants'; import {HELP_ARTICLE_ANON_SUBMISSIONS_URL} from 'js/constants'; -import {useLocalObservable} from 'mobx-react'; +import {useSession} from '../stores/useSession'; bem.AccountSettings = makeBem(null, 'account-settings', 'form'); bem.AccountSettings__left = makeBem(bem.AccountSettings, 'left'); @@ -40,35 +40,31 @@ const AccountSettings = () => { >({}); // We're verifying that the user is logged in so we can consider the current account is from a valid logged user - const {currentAccount} = useLocalObservable( - () => { - return {currentAccount: sessionStore.isLoggedIn && (sessionStore.currentAccount as AccountResponse)}; - } - ); + const {currentLoggedAccount} = useSession(); useEffect(() => { - if (!currentAccount) { + if (!currentLoggedAccount) { return; } setFormFields({ - name: currentAccount.extra_details.name, - organization_type: currentAccount.extra_details.organization_type, - organization: currentAccount.extra_details.organization, - organization_website: currentAccount.extra_details.organization_website, - sector: currentAccount.extra_details.sector, - gender: currentAccount.extra_details.gender, - bio: currentAccount.extra_details.bio, - city: currentAccount.extra_details.city, - country: currentAccount.extra_details.country, - require_auth: currentAccount.extra_details.require_auth, - twitter: currentAccount.extra_details.twitter, - linkedin: currentAccount.extra_details.linkedin, - instagram: currentAccount.extra_details.instagram, + name: currentLoggedAccount.extra_details.name, + organization_type: currentLoggedAccount.extra_details.organization_type, + organization: currentLoggedAccount.extra_details.organization, + organization_website: currentLoggedAccount.extra_details.organization_website, + sector: currentLoggedAccount.extra_details.sector, + gender: currentLoggedAccount.extra_details.gender, + bio: currentLoggedAccount.extra_details.bio, + city: currentLoggedAccount.extra_details.city, + country: currentLoggedAccount.extra_details.country, + require_auth: currentLoggedAccount.extra_details.require_auth, + twitter: currentLoggedAccount.extra_details.twitter, + linkedin: currentLoggedAccount.extra_details.linkedin, + instagram: currentLoggedAccount.extra_details.instagram, newsletter_subscription: - currentAccount.extra_details.newsletter_subscription, + currentLoggedAccount.extra_details.newsletter_subscription, }); - }, [currentAccount]); + }, [currentLoggedAccount]); usePrompt({ when: !isPristine, @@ -130,7 +126,7 @@ const AccountSettings = () => { - {currentAccount && ( + {currentLoggedAccount && ( Date: Thu, 28 Nov 2024 09:18:52 -0300 Subject: [PATCH 09/11] Using new useSession hook --- jsapp/js/account/accountSettingsRoute.tsx | 2 -- jsapp/js/tos/tosForm.component.tsx | 10 ++++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettingsRoute.tsx index 9ce71f32c8..a785f66ea3 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettingsRoute.tsx @@ -7,7 +7,6 @@ import bem, {makeBem} from 'js/bem'; import sessionStore from 'js/stores/session'; import './accountSettings.scss'; import {notify} from 'js/utils'; -import type {AccountResponse} from '../dataInterface'; import {dataInterface} from '../dataInterface'; import AccountFieldsEditor from './accountFieldsEditor.component'; import Avatar from 'js/components/common/avatar'; @@ -39,7 +38,6 @@ const AccountSettings = () => { Partial >({}); - // We're verifying that the user is logged in so we can consider the current account is from a valid logged user const {currentLoggedAccount} = useSession(); useEffect(() => { diff --git a/jsapp/js/tos/tosForm.component.tsx b/jsapp/js/tos/tosForm.component.tsx index 9acc99745e..737f9b7ebc 100644 --- a/jsapp/js/tos/tosForm.component.tsx +++ b/jsapp/js/tos/tosForm.component.tsx @@ -21,7 +21,7 @@ import type { AccountFieldsErrors, } from 'js/account/account.constants'; import {currentLang, notify} from 'js/utils'; -import {useLocalObservable} from 'mobx-react'; +import {useSession} from '../stores/useSession'; /** A slug for the `sitewide_messages` endpoint */ const TOS_SLUG = 'terms_of_service'; @@ -72,9 +72,7 @@ export default function TOSForm() { fieldsToShow.push('newsletter_subscription'); } - const currentAccount = useLocalObservable( - () => sessionStore.currentAccount as AccountResponse - ); + const {currentLoggedAccount} = useSession(); // Get TOS message from endpoint useEffect(() => { @@ -117,7 +115,7 @@ export default function TOSForm() { // (including the non-required ones that will be hidden, but passed to the API // so that they will not get erased). useEffect(() => { - if (!currentAccount) { + if (!currentLoggedAccount) { return; } @@ -138,7 +136,7 @@ export default function TOSForm() { instagram: data.extra_details.instagram, newsletter_subscription: data.extra_details.newsletter_subscription, }); - }, [currentAccount]); + }, [currentLoggedAccount]); const onFieldChange = (fieldName: string, value: string | boolean) => { setFormFields({ From 2fd2f43df03f25e7117620483e6a2061b7d69800 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 28 Nov 2024 10:49:57 -0300 Subject: [PATCH 10/11] cleanup sessionStore usage --- jsapp/js/account/accountSettingsRoute.tsx | 3 +- jsapp/js/tos/tosForm.component.tsx | 48 ++++++++++------------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettingsRoute.tsx index a785f66ea3..0545131521 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettingsRoute.tsx @@ -4,7 +4,6 @@ import Button from 'js/components/common/button'; import InlineMessage from 'js/components/common/inlineMessage'; import {unstable_usePrompt as usePrompt} from 'react-router-dom'; import bem, {makeBem} from 'js/bem'; -import sessionStore from 'js/stores/session'; import './accountSettings.scss'; import {notify} from 'js/utils'; import {dataInterface} from '../dataInterface'; @@ -105,7 +104,7 @@ const AccountSettings = () => { setIsPristine(false); }; - const accountName = sessionStore.currentAccount.username; + const accountName = currentLoggedAccount?.username || ''; return ( diff --git a/jsapp/js/tos/tosForm.component.tsx b/jsapp/js/tos/tosForm.component.tsx index 737f9b7ebc..48213c236a 100644 --- a/jsapp/js/tos/tosForm.component.tsx +++ b/jsapp/js/tos/tosForm.component.tsx @@ -2,14 +2,9 @@ import type React from 'react'; import {useState, useEffect} from 'react'; import Button from 'js/components/common/button'; import envStore from 'js/envStore'; -import sessionStore from 'js/stores/session'; import {fetchGet, fetchPatch, fetchPost, handleApiFail} from 'js/api'; import styles from './tosForm.module.scss'; -import type { - AccountResponse, - FailResponse, - PaginatedResponse, -} from 'js/dataInterface'; +import type {FailResponse, PaginatedResponse} from 'js/dataInterface'; import LoadingSpinner from 'js/components/common/loadingSpinner'; import { getInitialAccountFieldsValues, @@ -72,7 +67,7 @@ export default function TOSForm() { fieldsToShow.push('newsletter_subscription'); } - const {currentLoggedAccount} = useSession(); + const {currentLoggedAccount, logOut} = useSession(); // Get TOS message from endpoint useEffect(() => { @@ -119,22 +114,23 @@ export default function TOSForm() { return; } - const data = sessionStore.currentAccount as AccountResponse; setFormFields({ - name: data.extra_details.name, - organization: data.extra_details.organization, - organization_website: data.extra_details.organization_website, - organization_type: data.extra_details.organization_type, - sector: data.extra_details.sector, - gender: data.extra_details.gender, - bio: data.extra_details.bio, - city: data.extra_details.city, - country: data.extra_details.country, - require_auth: data.extra_details.require_auth, - twitter: data.extra_details.twitter, - linkedin: data.extra_details.linkedin, - instagram: data.extra_details.instagram, - newsletter_subscription: data.extra_details.newsletter_subscription, + name: currentLoggedAccount.extra_details.name, + organization: currentLoggedAccount.extra_details.organization, + organization_website: + currentLoggedAccount.extra_details.organization_website, + organization_type: currentLoggedAccount.extra_details.organization_type, + sector: currentLoggedAccount.extra_details.sector, + gender: currentLoggedAccount.extra_details.gender, + bio: currentLoggedAccount.extra_details.bio, + city: currentLoggedAccount.extra_details.city, + country: currentLoggedAccount.extra_details.country, + require_auth: currentLoggedAccount.extra_details.require_auth, + twitter: currentLoggedAccount.extra_details.twitter, + linkedin: currentLoggedAccount.extra_details.linkedin, + instagram: currentLoggedAccount.extra_details.instagram, + newsletter_subscription: + currentLoggedAccount.extra_details.newsletter_subscription, }); }, [currentLoggedAccount]); @@ -203,16 +199,12 @@ export default function TOSForm() { function leaveForm() { setIsFormPending(true); - sessionStore.logOut(); + logOut(); } // We are waiting for few pieces of data: the message, fields definitions from // environment endpoint and fields data from me endpoint - if ( - !announcementMessage || - !envStore.isReady || - !sessionStore.isAuthStateKnown - ) { + if (!announcementMessage || !envStore.isReady || !currentLoggedAccount) { return ; } From 0b0702e34909adb894ea307f6beaefcf56c2a9a3 Mon Sep 17 00:00:00 2001 From: Paulo Amorim Date: Thu, 28 Nov 2024 10:58:28 -0300 Subject: [PATCH 11/11] Refresh data after update --- jsapp/js/account/accountSettingsRoute.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jsapp/js/account/accountSettingsRoute.tsx b/jsapp/js/account/accountSettingsRoute.tsx index 0545131521..bdaf7368ce 100644 --- a/jsapp/js/account/accountSettingsRoute.tsx +++ b/jsapp/js/account/accountSettingsRoute.tsx @@ -37,7 +37,7 @@ const AccountSettings = () => { Partial >({}); - const {currentLoggedAccount} = useSession(); + const {currentLoggedAccount, refreshAccount} = useSession(); useEffect(() => { if (!currentLoggedAccount) { @@ -86,6 +86,7 @@ const AccountSettings = () => { .patchProfile(patchData) .done(() => { onUpdateComplete(); + refreshAccount(); }) .fail((...args: any) => { onUpdateFail(args[0].responseJSON);