Skip to content

Commit

Permalink
Edit profile form for delegates (thewca#10456)
Browse files Browse the repository at this point in the history
* Edit profile form for delegates

* Review changes

* Review changes

* Review changes

---------

Co-authored-by: Gregor Billing <[email protected]>
  • Loading branch information
danieljames-dj and gregorbg authored Jan 13, 2025
1 parent a69839c commit d3889ef
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 30 deletions.
15 changes: 10 additions & 5 deletions app/controllers/contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,19 @@ def edit_profile_action
edit_profile_reason = formValues[:editProfileReason]
attachment = params[:attachment]
wca_id = formValues[:wcaId]
person = Person.find_by(wca_id: wca_id)

return render status: :unauthorized, json: { error: "Cannot request profile change without login" } unless current_user.present?
if current_user.nil?
return render status: :unauthorized, json: { error: "Cannot request profile change without login" }
elsif current_user.wca_id != wca_id && !current_user.has_permission?(:can_request_to_edit_others_profile)
return render status: :unauthorized, json: { error: "Cannot request to change others profile" }
end

profile_to_edit = {
name: current_user.person.name,
country_iso2: current_user.person.country_iso2,
gender: current_user.person.gender,
dob: current_user.person.dob,
name: person.name,
country_iso2: person.country_iso2,
gender: person.gender,
dob: person.dob,
}
changes_requested = Person.fields_edit_requestable
.reject { |field| profile_to_edit[field].to_s == edited_profile_details[field].to_s }
Expand Down
3 changes: 3 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,9 @@ def permissions
can_access_panels: {
scope: panels_with_access,
},
can_request_to_edit_others_profile: {
scope: any_kind_of_delegate? ? "*" : [],
},
}
if banned?
permissions[:can_attend_competitions][:scope] = []
Expand Down
53 changes: 44 additions & 9 deletions app/webpacker/components/ContactEditProfilePage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,37 @@ import Loading from '../Requests/Loading';
import { fetchJsonOrError } from '../../lib/requests/fetchWithAuthenticityToken';
import Errored from '../Requests/Errored';
import EditProfileForm from './EditProfileForm';
import useLoggedInUserPermissions from '../../lib/hooks/useLoggedInUserPermissions';
import useQueryParams from '../../lib/hooks/useQueryParams';
import useInputState from '../../lib/hooks/useInputState';
import { IdWcaSearch } from '../SearchWidget/WcaSearch';
import SEARCH_MODELS from '../SearchWidget/SearchModel';

const CONTACT_EDIT_PROFILE_QUERY_CLIENT = new QueryClient();

export default function ContactEditProfilePage({ loggedInUserId, recaptchaPublicKey }) {
const [queryParams] = useQueryParams();
const editOthersProfileMode = Boolean(queryParams.editOthersProfile);
const { data: loggedInUserData, isLoading, isError } = useQuery({
queryKey: ['userData'],
queryFn: () => fetchJsonOrError(apiV0Urls.users.me.userDetails),
enabled: !!loggedInUserId,
enabled: (
// If the user is logged in, then we need to fetch their WCA ID.
!!loggedInUserId
// If the user is not editing somebody else's profile, then we need to fetch their own WCA ID.
|| !editOthersProfileMode
),
}, CONTACT_EDIT_PROFILE_QUERY_CLIENT);
const wcaId = loggedInUserData?.data?.user?.wca_id;
const { loggedInUserPermissions, loading } = useLoggedInUserPermissions();
const [inputWcaId, setInputWcaId] = useInputState();
const [contactSuccess, setContactSuccess] = useState(false);

if (isLoading) return <Loading />;
const wcaId = editOthersProfileMode ? inputWcaId : loggedInUserData?.data?.user?.wca_id;

if (isLoading || loading) return <Loading />;
if (isError) return <Errored />;
if (!loggedInUserData) {

if (!loggedInUserId) {
return (
<Message error>
<I18nHTMLTranslate i18nKey="page.contact_edit_profile.not_logged_in_error" />
Expand All @@ -36,6 +52,13 @@ export default function ContactEditProfilePage({ loggedInUserId, recaptchaPublic
</Message>
);
}
if (editOthersProfileMode && !loggedInUserPermissions.canRequestToEditOthersProfile) {
return (
<Message error>
<I18nHTMLTranslate i18nKey="page.contact_edit_profile.no_permission_error" />
</Message>
);
}
if (contactSuccess) {
return (
<Message
Expand All @@ -48,11 +71,23 @@ export default function ContactEditProfilePage({ loggedInUserId, recaptchaPublic
return (
<Container text>
<Header as="h2">{I18n.t('page.contact_edit_profile.title')}</Header>
<EditProfileForm
wcaId={wcaId}
onContactSuccess={() => setContactSuccess(true)}
recaptchaPublicKey={recaptchaPublicKey}
/>
{editOthersProfileMode && (
<IdWcaSearch
model={SEARCH_MODELS.person}
multiple={false}
value={inputWcaId}
onChange={setInputWcaId}
disabled={!!inputWcaId}
label={I18n.t('page.contact_edit_profile.form.wca_id_search.label')}
/>
)}
{wcaId && (
<EditProfileForm
wcaId={wcaId}
onContactSuccess={() => setContactSuccess(true)}
recaptchaPublicKey={recaptchaPublicKey}
/>
)}
</Container>
);
}
9 changes: 6 additions & 3 deletions app/webpacker/components/ContactsPage/ContactForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
} from 'semantic-ui-react';
import ReCAPTCHA from 'react-google-recaptcha';
import _ from 'lodash';
import { contactUrl, contactEditProfileActionUrl } from '../../lib/requests/routes.js.erb';
import { contactUrl, contactEditProfileActionUrl, contactEditOthersProfileActionUrl } from '../../lib/requests/routes.js.erb';
import useSaveAction from '../../lib/hooks/useSaveAction';
import I18n from '../../lib/i18n';
import UserData from './UserData';
Expand All @@ -31,9 +31,12 @@ const getFormRedirection = (formValues) => {
if (formValues[CONTACT_RECIPIENTS_MAP.wrt].queryType === 'edit_profile') {
return contactEditProfileActionUrl;
}
if (formValues[CONTACT_RECIPIENTS_MAP.wrt].queryType === 'edit_others_profile') {
return contactEditOthersProfileActionUrl;
}
}
return null;
}
};

export default function ContactForm({
loggedInUserData,
Expand All @@ -47,7 +50,7 @@ export default function ContactForm({
const contactFormState = useStore();
const dispatch = useDispatch();
const { formValues: { contactRecipient: selectedContactRecipient, userData } } = contactFormState;
const formRedirection = useMemo(() => getFormRedirection(contactFormState.formValues));
const formRedirection = getFormRedirection(contactFormState.formValues);

const isFormValid = (
selectedContactRecipient && userData.name && userData.email && (captchaValue || formRedirection)
Expand Down
57 changes: 44 additions & 13 deletions app/webpacker/components/ContactsPage/SubForms/Wrt/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,29 @@ import I18n from '../../../../lib/i18n';
import { useDispatch, useStore } from '../../../../lib/providers/StoreProvider';
import { updateSectionData } from '../../store/actions';
import OtherQuery from './OtherQuery';
import Loading from '../../../Requests/Loading';
import useLoggedInUserPermissions from '../../../../lib/hooks/useLoggedInUserPermissions';

const SECTION = 'wrt';
const QUERY_TYPES = ['edit_profile', 'report_result_issue', 'other_query'];
const QUERY_TYPES_MAP = _.keyBy(QUERY_TYPES, _.camelCase);
const QUERY_TYPES = [
{
key: 'edit_profile',
requiredPermission: null,
},
{
key: 'edit_others_profile',
requiredPermission: 'canRequestToEditOthersProfile',
},
{
key: 'report_result_issue',
requiredPermission: null,
},
{
key: 'other_query',
requiredPermission: null,
},
];
const QUERY_TYPES_MAP = _.keyBy(QUERY_TYPES, (item) => (_.camelCase(item.key)));

export default function Wrt() {
const { formValues: { wrt } } = useStore();
Expand All @@ -18,26 +37,38 @@ export default function Wrt() {
const handleFormChange = (_, { name, value }) => dispatch(
updateSectionData(SECTION, name, value),
);
const { loggedInUserPermissions, loading: permissionsLoading } = useLoggedInUserPermissions();

const QueryForm = useMemo(() => {
if (!selectedQueryType || selectedQueryType === QUERY_TYPES_MAP.editProfile) return null;
if (
!selectedQueryType
|| [
QUERY_TYPES_MAP.editProfile.key,
QUERY_TYPES_MAP.editOthersProfile.key,
].includes(selectedQueryType)) {
return null;
}
return OtherQuery;
}, [selectedQueryType]);

if (permissionsLoading) return <Loading />;

return (
<>
<FormGroup grouped>
<div>{I18n.t('page.contacts.form.wrt.query_type.label')}</div>
{QUERY_TYPES.map((queryType) => (
<FormField key={queryType}>
<Radio
label={I18n.t(`page.contacts.form.wrt.query_type.${queryType}.label`)}
name="queryType"
value={queryType}
checked={selectedQueryType === queryType}
onChange={handleFormChange}
/>
</FormField>
{QUERY_TYPES.map(({ key: queryTypeKey, requiredPermission }) => (
(!requiredPermission || loggedInUserPermissions[requiredPermission]) && (
<FormField key={queryTypeKey}>
<Radio
label={I18n.t(`page.contacts.form.wrt.query_type.${queryTypeKey}.label`)}
name="queryType"
value={queryTypeKey}
checked={selectedQueryType === queryTypeKey}
onChange={handleFormChange}
/>
</FormField>
)
))}
</FormGroup>
{QueryForm && <QueryForm />}
Expand Down
1 change: 1 addition & 0 deletions app/webpacker/lib/hooks/useLoggedInUserPermissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function useLoggedInUserPermissions() {
const loggedInUserPermissions = React.useMemo(() => ({
canViewDelegateAdminPage: Boolean(data?.can_view_delegate_admin_page.scope === '*'),
canEditGroup: (groupId) => Boolean(data?.can_edit_groups.scope === '*' || data?.can_edit_groups.scope.includes(groupId)),
canRequestToEditOthersProfile: Boolean(data?.can_request_to_edit_others_profile.scope === '*'),
}), [data]);

return { loggedInUserPermissions, loading };
Expand Down
2 changes: 2 additions & 0 deletions app/webpacker/lib/requests/routes.js.erb
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ export const contactUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers

export const contactEditProfileActionUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.contact_edit_profile_action_path) %>`

export const contactEditOthersProfileActionUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.contact_edit_profile_action_path) %>?editOthersProfile=true`

export const contactRecipientUrl = (contactRecipient) => `<%= CGI.unescape(Rails.application.routes.url_helpers.contact_path(contactRecipient: "${contactRecipient}")) %>`

export const contactCompetitionUrl = (competitionId, message) => `<%= CGI.unescape(Rails.application.routes.url_helpers.contact_path(competitionId: "${competitionId}", contactRecipient: "competition", message: "${message}")) %>`
Expand Down
5 changes: 5 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,8 @@ en:
label: "Which of the following best describes your inquiry?"
edit_profile:
label: "Edit my profile data (name, country, gender or DOB)"
edit_others_profile:
label: "Edit someone else's profile data"
report_result_issue:
label: "Report an issue in a result"
other_query:
Expand Down Expand Up @@ -873,11 +875,14 @@ en:
title: "Edit Profile Form"
not_logged_in_error: "Looks like you are not logged in. Please log in to edit your profile. For any other queries, please <a href=\"https://www.worldcubeassociation.org/contact?contactRecipient=wrt\">contact us</a>."
no_profile_error: "Looks like you don't have a WCA profile yet because you have not participated in any competitions. For any other queries, please <a href=\"https://www.worldcubeassociation.org/contact?contactRecipient=wrt\">contact us</a>."
no_permission_error: "You don't have permission to request edit others profile."
form:
contact_email:
label: "Contact Email"
wca_id:
label: "Enter WCA ID"
wca_id_search:
label: "Search for the WCA ID that you want to edit."
edit_reason:
label: "Reason for the change"
proof_attach:
Expand Down

0 comments on commit d3889ef

Please sign in to comment.