mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(ui) refactored profile-completion screen to use more accurate messages
This commit is contained in:
parent
69d595e9f6
commit
040858ac1f
@ -0,0 +1,70 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { ActiveProviderResponse } from '../../models/authentication-provider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { User } from '../../models/user';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { IApplication } from '../../models/application';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import { SettingName } from '../../models/setting';
|
||||
import UserLib from '../../lib/user';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface CompletionHeaderInfoProps {
|
||||
user: User,
|
||||
activeProvider: ActiveProviderResponse,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component will show an information message, on the profile completion page.
|
||||
*/
|
||||
export const CompletionHeaderInfo: React.FC<CompletionHeaderInfoProps> = ({ user, activeProvider, onError }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
const [settings, setSettings] = React.useState<Map<SettingName, string>>(null);
|
||||
|
||||
const userLib = new UserLib(user);
|
||||
|
||||
useEffect(() => {
|
||||
SettingAPI.query([SettingName.NameGenre, SettingName.FablabName]).then(setSettings).catch(onError);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="completion-header-info">
|
||||
{activeProvider?.providable_type === 'DatabaseProvider' && <div className="header-info--local-database">
|
||||
<p>{t('app.logged.profile_completion.completion_header_info.rules_changed')}</p>
|
||||
</div>}
|
||||
{activeProvider?.providable_type !== 'DatabaseProvider' && <div className="header-info--sso">
|
||||
<p className="intro">
|
||||
<span>
|
||||
{t('app.logged.profile_completion.completion_header_info.sso_intro', {
|
||||
GENDER: settings?.get(SettingName.NameGenre),
|
||||
NAME: settings?.get(SettingName.FablabName)
|
||||
})}
|
||||
</span>
|
||||
<span className="provider-name">
|
||||
{activeProvider?.name}
|
||||
{userLib.ssoEmail() && <span className="user-email">({ userLib.ssoEmail() })</span>}
|
||||
</span>
|
||||
</p>
|
||||
{userLib.hasDuplicate() && <p className="duplicate-email-info">
|
||||
{t('app.logged.profile_completion.completion_header_info.duplicate_email_info')}
|
||||
</p>}
|
||||
{!userLib.hasDuplicate() && <p className="details-needed-info">
|
||||
{t('app.logged.profile_completion.completion_header_info.details_needed_info')}
|
||||
</p>}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CompletionHeaderInfoWrapper: React.FC<CompletionHeaderInfoProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<CompletionHeaderInfo {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('completionHeaderInfo', react2angular(CompletionHeaderInfoWrapper, ['user', 'activeProvider', 'onError']));
|
@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { User } from '../../models/user';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { ActiveProviderResponse } from '../../models/authentication-provider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { UserProfileForm } from '../user/user-profile-form';
|
||||
import UserLib from '../../lib/user';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import Authentication from '../../api/authentication';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface ProfileFormOptionProps {
|
||||
user: User,
|
||||
activeProvider: ActiveProviderResponse,
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (user: User) => void,
|
||||
}
|
||||
|
||||
export const ProfileFormOption: React.FC<ProfileFormOptionProps> = ({ user, activeProvider, onError, onSuccess }) => {
|
||||
const { t } = useTranslation('logged');
|
||||
|
||||
const userLib = new UserLib(user);
|
||||
|
||||
/**
|
||||
* Route the current user to the interface provided by the authentication provider, to update his profile.
|
||||
*/
|
||||
const redirectToSsoProfile = (): void => {
|
||||
window.open(activeProvider.link_to_sso_profile, '_blank');
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect and re-connect the user to the SSO to force the synchronisation of the profile's data
|
||||
*/
|
||||
function syncProfile () {
|
||||
Authentication.logout().then(() => {
|
||||
window.location.href = activeProvider.link_to_sso_connect;
|
||||
}).catch(onError);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="profile-form-option">
|
||||
<h3>{t('app.logged.profile_completion.profile_form_option.title')}</h3>
|
||||
{!userLib.hasDuplicate() && <div className="normal-flow">
|
||||
<p>{t('app.logged.profile_completion.profile_form_option.please_fill')}</p>
|
||||
<p className="disabled-fields-info">{t('app.logged.profile_completion.profile_form_option.disabled_data_from_sso', { NAME: activeProvider?.name })}</p>
|
||||
<p className="confirm-instructions">
|
||||
<HtmlTranslate trKey="app.logged.profile_completion.profile_form_option.confirm_instructions_html" />
|
||||
</p>
|
||||
<UserProfileForm onError={onError}
|
||||
action="update"
|
||||
user={user}
|
||||
onSuccess={onSuccess}
|
||||
size="small"
|
||||
showGroupInput
|
||||
showTermsAndConditionsInput />
|
||||
</div>}
|
||||
{userLib.hasDuplicate() && <div className="duplicate-email">
|
||||
<p className="duplicate-info">
|
||||
<HtmlTranslate trKey="app.logged.profile_completion.profile_form_option.duplicate_email_html"
|
||||
options={{ EMAIL: userLib.ssoEmail(), PROVIDER: activeProvider?.name }} />
|
||||
</p>
|
||||
<FabButton onClick={redirectToSsoProfile} icon={<i className="fa fa-edit"/>}>
|
||||
{t('app.logged.profile_completion.profile_form_option.edit_profile')}
|
||||
</FabButton>
|
||||
<p className="after-edition-info">
|
||||
<HtmlTranslate trKey="app.logged.profile_completion.profile_form_option.after_edition_info_html" />
|
||||
</p>
|
||||
<FabButton onClick={syncProfile} icon={<i className="fa fa-refresh"/>}>
|
||||
{t('app.logged.profile_completion.profile_form_option.sync_profile')}
|
||||
</FabButton>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileFormOptionWrapper: React.FC<ProfileFormOptionProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<ProfileFormOption {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('profileFormOption', react2angular(ProfileFormOptionWrapper, ['user', 'activeProvider', 'onError', 'onSuccess']));
|
@ -21,7 +21,7 @@ export const EditSocials = <TFieldValues extends FieldValues>({ register, setVal
|
||||
// regular expression to validate the the input fields
|
||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/;
|
||||
|
||||
const initSelectedNetworks = networks.filter(el => el.url !== '');
|
||||
const initSelectedNetworks = networks.filter(el => !['', null, undefined].includes(el.url));
|
||||
const [selectedNetworks, setSelectedNetworks] = useState(initSelectedNetworks);
|
||||
const selectNetwork = (network) => {
|
||||
setSelectedNetworks([...selectedNetworks, network]);
|
||||
|
@ -32,4 +32,29 @@ export default class UserLib {
|
||||
}
|
||||
return userNetworks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the email given by the SSO provider, parsed if needed
|
||||
* @return {String} E-mail of the current user
|
||||
*/
|
||||
ssoEmail = (): string => {
|
||||
const { email } = this.user;
|
||||
if (email) {
|
||||
const duplicate = email.match(/^<([^>]+)>.{20}-duplicate$/);
|
||||
if (duplicate) {
|
||||
return duplicate[1];
|
||||
}
|
||||
}
|
||||
return email;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test if the user's mail is marked as duplicate
|
||||
*/
|
||||
hasDuplicate = (): boolean => {
|
||||
const { email } = this.user;
|
||||
if (email) {
|
||||
return !(email.match(/^<([^>]+)>.{20}-duplicate$/) === null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -71,5 +71,4 @@ export interface ActiveProviderResponse extends AuthenticationProvider {
|
||||
mapping: Array<string>,
|
||||
link_to_sso_profile: string,
|
||||
link_to_sso_connect: string,
|
||||
domain?: string
|
||||
}
|
||||
|
@ -71,6 +71,8 @@
|
||||
@import "modules/pricing/spaces/delete-extended-price";
|
||||
@import "modules/pricing/spaces/edit-extended-price";
|
||||
@import "modules/pricing/spaces/spaces-pricing";
|
||||
@import "modules/profile-completion/completion-header-info";
|
||||
@import "modules/profile-completion/profile-form-option";
|
||||
@import "modules/select-gateway-modal";
|
||||
@import "modules/settings/check-list-setting";
|
||||
@import "modules/subscriptions/free-extend-modal";
|
||||
|
@ -0,0 +1,17 @@
|
||||
.completion-header-info {
|
||||
padding: 15px;
|
||||
background-color: #f4f3f3;
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.provider-name {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-left: 40px;
|
||||
|
||||
.user-email {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
.profile-form-option {
|
||||
.disabled-fields-info,
|
||||
.duplicate-info {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.after-edition-info {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
@ -101,7 +101,6 @@
|
||||
<section class="panel panel-default bg-light m p-lg row" ng-if="hasSsoFields()">
|
||||
<div class="panel-heading">
|
||||
<h2>
|
||||
<img class="v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<span class="v-middle">{{activeProvider.name}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
@ -21,17 +21,9 @@
|
||||
|
||||
<div class="row no-gutter">
|
||||
<div class="col-sm-12 col-md-12 b-r">
|
||||
<div class="row" ng-hide="hideNewAccountConfirmation()">
|
||||
<div class="row">
|
||||
<div class="col-md-offset-2 col-md-8 m-t-md">
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
{{ 'app.logged.profile_completion.you_ve_just_created_a_new_account_on_the_fablab_by_logging_from' | translate:{ GENDER: nameGenre, NAME: fablabName } }}<br/>
|
||||
<img class="m-l v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<strong class="v-middle">{{activeProvider.name}} <span ng-if="ssoEmail()">({{ssoEmail()}})</span></strong><br/>
|
||||
<p class="m-t-md" ng-hide="hasDuplicate()" translate>{{ 'app.logged.profile_completion.we_need_some_more_details' }}.</p>
|
||||
<p class="m-t-md" ng-show="hasDuplicate()" translate>{{ 'app.logged.profile_completion.your_email_is_already_used_by_another_account_on_the_platform' }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<completion-header-info on-error="onError" user="user" active-provider="activeProvider" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row col-md-2 col-md-offset-5 hidden-sm hidden-xs" ng-hide="user.merged_at || hideNewAccountConfirmation()">
|
||||
@ -43,72 +35,16 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="m-lg panel panel-default bg-light pos-rlt" ng-hide="hasDuplicate()">
|
||||
<div class="m-lg panel panel-default bg-light pos-rlt">
|
||||
<div ng-class="{'disabling-overlay' : !!user.auth_token}">
|
||||
<div class="panel-body">
|
||||
<h3 translate ng-hide="hideNewAccountConfirmation()">{{ 'app.logged.profile_completion.new_on_this_platform' }}</h3>
|
||||
<p translate>{{ 'app.logged.profile_completion.please_fill_the_following_form'}}.</p>
|
||||
<p class="text-italic">{{ 'app.logged.profile_completion.some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified' | translate:{NAME:activeProvider.name} }}.<br/>
|
||||
{{ 'app.logged.profile_completion.then_click_on_' | translate }} <strong translate>{{ 'app.shared.buttons.confirm_changes' }}</strong> {{ 'app.logged.profile_completion._to_start_using_the_application' | translate }}.</p>
|
||||
<profile-form-option on-error="onError"
|
||||
on-success="onSuccess"
|
||||
user="user"
|
||||
active-provider="activeProvider" />
|
||||
</div>
|
||||
<form role="form"
|
||||
name="userForm"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
novalidate>
|
||||
|
||||
<section>
|
||||
|
||||
<div class="panel-body m-r">
|
||||
<!-- common fields -->
|
||||
<user-profile-form user="user"
|
||||
size="'small'"
|
||||
action="'update'"
|
||||
on-error="onError"
|
||||
on-success="onSuccess"
|
||||
show-group-input="true"
|
||||
show-terms-and-conditions-input="true" />
|
||||
|
||||
</div> <!-- ./panel-body -->
|
||||
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<section class="m-lg panel panel-default bg-light pos-rlt" ng-show="hasDuplicate()">
|
||||
<div ng-class="{'disabling-overlay' : !!user.auth_token}">
|
||||
<div class="panel-body">
|
||||
<h3 translate>{{ 'app.logged.profile_completion.new_on_this_platform' }}</h3>
|
||||
<p class="text-italic">
|
||||
{{ 'app.logged.profile_completion.your_email_' | translate }}
|
||||
<strong>({{ssoEmail()}})</strong>
|
||||
{{ 'app.logged.profile_completion._is_currently_associated_with_another_account_on_this_platform' | translate }}
|
||||
{{ 'app.logged.profile_completion.please_click_to_change_email_associated_with_your_PROVIDER_account' | translate:{PROVDER: activeProvider.name} }}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-href="{{activeProvider.link_to_sso_profile}}" target="_blank">
|
||||
<i class="fa fa-edit"></i> {{ 'app.logged.profile_completion.change_my_data' | translate }}
|
||||
</a>
|
||||
<p class="text-italic">
|
||||
{{ 'app.logged.profile_completion.once_your_data_are_up_to_date_' | translate }}
|
||||
<strong translate>{{ 'app.logged.profile_completion._click_on_the_synchronization_button_opposite_' }}</strong>
|
||||
{{ 'app.logged.profile_completion.or' | translate}}
|
||||
<strong translate>{{ 'app.logged.profile_completion._disconnect_then_reconnect_' }}</strong>
|
||||
{{ 'app.logged.profile_completion._for_your_changes_to_take_effect' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-click="syncProfile()">
|
||||
<i class="fa fa-refresh"></i> {{ 'app.logged.profile_completion.sync_my_profile' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="row col-xs-2 col-xs-offset-5 hidden-md hidden-lg" ng-hide="hideNewAccountConfirmation()">
|
||||
<p class="font-felt fleche-left text-lg upper text-center">
|
||||
|
@ -70,7 +70,7 @@ module SingleSignOnConcern
|
||||
profile[sso_mapping[8..-1].to_sym] = data unless data.nil?
|
||||
end
|
||||
end
|
||||
return if data.nil? || mapped_from_sso.include?(sso_mapping)
|
||||
return if data.nil? || mapped_from_sso&.include?(sso_mapping)
|
||||
|
||||
self.mapped_from_sso = [mapped_from_sso, sso_mapping].compact.join(',')
|
||||
end
|
||||
|
@ -5,8 +5,4 @@
|
||||
class OAuth2Provider < ApplicationRecord
|
||||
has_one :auth_provider, as: :providable
|
||||
|
||||
def domain
|
||||
URI(base_url).scheme + '://' + URI(base_url).host
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -11,5 +11,3 @@ if @provider.providable_type == DatabaseProvider.name
|
||||
else
|
||||
json.link_to_sso_connect '/sso-redirect'
|
||||
end
|
||||
|
||||
json.domain @provider.providable.domain if @provider.providable_type == OAuth2Provider.name
|
||||
|
@ -4,18 +4,7 @@ en:
|
||||
#user's profile completion page when logging from an SSO provider
|
||||
profile_completion:
|
||||
confirm_your_new_account: "Confirm your new account"
|
||||
you_ve_just_created_a_new_account_on_the_fablab_by_logging_from: "You've just created a new account on {GENDER, select, neutral{} other{the}} {NAME}, by logging from"
|
||||
we_need_some_more_details: "To finalize the platform setup, we need some more details"
|
||||
your_email_is_already_used_by_another_account_on_the_platform: "It looks like your email address is already used by another user. Check your email address and please input below the code sent to you."
|
||||
or: "or"
|
||||
please_fill_the_following_form: "Please fill the following form"
|
||||
some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified: "Some data may have already been provided by {NAME} and cannot be modified"
|
||||
then_click_on_: "Then click on"
|
||||
_to_start_using_the_application: "to start using the application"
|
||||
new_on_this_platform: "New on this platform?"
|
||||
your_email_: "Your email"
|
||||
_is_currently_associated_with_another_account_on_this_platform: "is currently associated with another account on this platform."
|
||||
please_click_to_change_email_associated_with_your_PROVIDER_account: "If it is not yours, please click on the following button to change the email associated with your {PROVIDER} account."
|
||||
do_you_already_have_an_account: "Do you already have an account?"
|
||||
do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Do not fill the form beside but specify here the code you've received by email, to recover your access."
|
||||
just_specify_code_here_to_recover_access: "Just specify here the code you've received by email to recover your access."
|
||||
@ -33,13 +22,21 @@ en:
|
||||
user_s_profile_is_required: "User's profile is required."
|
||||
i_ve_read_and_i_accept_: "I've read and I accept"
|
||||
_the_fablab_policy: "the FabLab policy"
|
||||
change_my_data: "Change my data"
|
||||
sync_my_profile: "Sync my profile"
|
||||
once_your_data_are_up_to_date_: "Once your data are up to date,"
|
||||
_click_on_the_synchronization_button_opposite_: "click on the synchronization button opposite"
|
||||
_disconnect_then_reconnect_: "disconnect then reconnect"
|
||||
_for_your_changes_to_take_effect: "for your changes to take effect."
|
||||
your_profile_has_been_successfully_updated: "Your profile has been successfully updated."
|
||||
completion_header_info:
|
||||
rules_changed: "Please fill the following form to update your profile and continue to use the platform."
|
||||
sso_intro: "You've just created a new account on {GENDER, select, neutral{} other{the}} {NAME}, by logging from"
|
||||
duplicate_email_info: "It looks like your email address is already used by another user. Check your email address and please input below the code you have received."
|
||||
details_needed_info: "To finalize your account, we need some more details."
|
||||
profile_form_option:
|
||||
title: "New on this platform?"
|
||||
please_fill: "Please fill in the following form to create your account."
|
||||
disabled_data_from_sso: "Some data may have already been provided by {NAME} and cannot be modified."
|
||||
confirm_instructions_html: "Once you are done, please click on <strong>Save</strong> to confirm your account and start using the application."
|
||||
duplicate_email_html: "It looks like your email address <strong>({EMAIL})</strong> is already associated with another account. If this account is not yours, please click on the following button to change the email associated with your {PROVIDER} account."
|
||||
edit_profile: "Change my data"
|
||||
after_edition_info_html: "Once your data are up to date, <strong>click on the synchronization button below</strong>, or <strong>disconnect then reconnect</strong> for your changes to take effect."
|
||||
sync_profile: "Sync my profile"
|
||||
dashboard:
|
||||
#dashboard: public profile
|
||||
profile:
|
||||
|
Loading…
x
Reference in New Issue
Block a user