Skip to content
Snippets Groups Projects
Unverified Commit ab3b4d05 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Migrate UserProfileForm to formik (#26231)

parent 481775a8
No related branches found
No related tags found
No related merge requests found
Showing
with 193 additions and 161 deletions
......@@ -62,7 +62,7 @@ export const createMockSettings = (opts?: Partial<Settings>): Settings => ({
"application-font": "Lato",
"application-font-files": [],
"available-fonts": [],
"available-locales": [],
"available-locales": null,
"enable-public-sharing": false,
"enable-xrays": false,
"email-configured?": false,
......
......@@ -42,7 +42,7 @@ export interface Settings {
"application-font": string;
"application-font-files": FontFile[] | null;
"available-fonts": string[];
"available-locales": LocaleData[] | undefined;
"available-locales": LocaleData[] | null;
"enable-public-sharing": boolean;
"enable-xrays": boolean;
"email-configured?": boolean;
......
......@@ -35,11 +35,7 @@ const UserPasswordForm = ({
onSubmit,
}: UserPasswordFormProps): JSX.Element => {
const initialValues = useMemo(
() => ({
old_password: "",
password: "",
password_confirm: "",
}),
() => ({ old_password: "", password: "", password_confirm: "" }),
[],
);
......
import { createThunkAction } from "metabase/lib/redux";
import Users from "metabase/entities/users";
import { User } from "metabase-types/api";
import { Dispatch } from "metabase-types/store";
import { UserProfileData } from "./types";
export const UPDATE_USER = "metabase/account/profile/UPDATE_USER";
export const updateUser = createThunkAction(
UPDATE_USER,
(user: User, data: UserProfileData) => async (dispatch: Dispatch) => {
await dispatch(Users.actions.update({ ...data, id: user.id }));
if (user.locale !== data.locale) {
window.location.reload();
}
},
);
import React, { useCallback } from "react";
import PropTypes from "prop-types";
import User from "metabase/entities/users";
const propTypes = {
user: PropTypes.object,
};
const UserProfileForm = ({ user }) => {
const handleSaved = useCallback(
values => {
if (user.locale !== values.locale) {
window.location.reload();
}
},
[user?.locale],
);
return (
<User.Form
user={user}
form={User.forms.user(user)}
onSaved={handleSaved}
overwriteOnInitialValuesChange
/>
);
};
UserProfileForm.propTypes = propTypes;
export default UserProfileForm;
import React, { useCallback, useMemo } from "react";
import { t } from "ttag";
import * as Yup from "yup";
import _ from "underscore";
import Form from "metabase/core/components/Form";
import FormProvider from "metabase/core/components/FormProvider";
import FormInput from "metabase/core/components/FormInput";
import FormSelect from "metabase/core/components/FormSelect";
import FormSubmitButton from "metabase/core/components/FormSubmitButton";
import FormErrorMessage from "metabase/core/components/FormErrorMessage";
import { LocaleData, User } from "metabase-types/api";
import { UserProfileData } from "../../types";
const SsoProfileSchema = Yup.object({
locale: Yup.string().nullable(true),
});
const LocalProfileSchema = SsoProfileSchema.shape({
first_name: Yup.string().max(100, t`must be 100 characters or less`),
last_name: Yup.string().max(100, t`must be 100 characters or less`),
email: Yup.string()
.required(t`required`)
.email(t`must be a valid email address`),
});
export interface UserProfileFormProps {
user: User;
locales: LocaleData[] | null;
isSsoUser: boolean;
onSubmit: (user: User, data: UserProfileData) => void;
}
const UserProfileForm = ({
user,
locales,
isSsoUser,
onSubmit,
}: UserProfileFormProps): JSX.Element => {
const initialValues = useMemo(() => getInitialValues(user), [user]);
const localeOptions = useMemo(() => getLocaleOptions(locales), [locales]);
const handleSubmit = useCallback(
(data: UserProfileData) => onSubmit(user, getSubmitValues(data)),
[user, onSubmit],
);
return (
<FormProvider
initialValues={initialValues}
validationSchema={isSsoUser ? SsoProfileSchema : LocalProfileSchema}
enableReinitialize
onSubmit={handleSubmit}
>
{({ dirty }) => (
<Form disabled={!dirty}>
{!isSsoUser && (
<>
<FormInput
name="first_name"
title={t`First name`}
placeholder={t`Johnny`}
fullWidth
/>
<FormInput
name="last_name"
title={t`Last name`}
placeholder={t`Appleseed`}
fullWidth
/>
<FormInput
name="email"
type="email"
title={t`Email`}
placeholder="nicetoseeyou@email.com"
fullWidth
/>
</>
)}
<FormSelect
name="locale"
title={t`Language`}
options={localeOptions}
/>
<FormSubmitButton title={t`Update`} disabled={!dirty} primary />
<FormErrorMessage />
</Form>
)}
</FormProvider>
);
};
const getInitialValues = (user: User): UserProfileData => {
return {
first_name: user.first_name || "",
last_name: user.last_name || "",
email: user.email,
locale: user.locale,
};
};
const getSubmitValues = (data: UserProfileData): UserProfileData => {
return {
...data,
first_name: data.first_name || null,
last_name: data.last_name || null,
};
};
const getLocaleOptions = (locales: LocaleData[] | null) => {
const options = _.chain(locales ?? [["en", "English"]])
.map(([value, name]) => ({ name, value }))
.sortBy(({ name }) => name)
.value();
return [{ name: t`Use site default`, value: null }, ...options];
};
export default UserProfileForm;
import { connect } from "react-redux";
import { getUser } from "metabase/selectors/user";
import { State } from "metabase-types/store";
import UserProfileForm from "../../components/UserProfileForm";
import { updateUser } from "../../actions";
import { getIsSsoUser, getLocales } from "../../selectors";
const mapStateToProps = state => ({
const mapStateToProps = (state: State) => ({
user: getUser(state),
locales: getLocales(state),
isSsoUser: getIsSsoUser(state),
});
export default connect(mapStateToProps)(UserProfileForm);
const mapDispatchToProps = {
onSubmit: updateUser,
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfileForm);
import { createSelector } from "reselect";
import { getUser } from "metabase/selectors/user";
import { PLUGIN_IS_PASSWORD_USER } from "metabase/plugins";
import { getSettings } from "metabase/selectors/settings";
export const getIsSsoUser = createSelector([getUser], user => {
return !PLUGIN_IS_PASSWORD_USER.every(predicate => predicate(user));
});
export const getLocales = createSelector([getSettings], settings => {
return settings["available-locales"];
});
export interface UserProfileData {
first_name: string | null;
last_name: string | null;
email: string;
locale: string | null;
}
import _ from "underscore";
import { t } from "ttag";
import MetabaseSettings from "metabase/lib/settings";
import MetabaseUtils from "metabase/lib/utils";
import {
PLUGIN_ADMIN_USER_FORM_FIELDS,
PLUGIN_IS_PASSWORD_USER,
} from "metabase/plugins";
import { PLUGIN_ADMIN_USER_FORM_FIELDS } from "metabase/plugins";
import validate from "metabase/lib/validate";
import FormGroupsWidget from "metabase/components/form/widgets/FormGroupsWidget";
const getNameFields = () => [
{
name: "first_name",
title: t`First name`,
placeholder: "Johnny",
autoFocus: true,
validate: validate.maxLength(100),
normalize: firstName => firstName || null,
},
{
name: "last_name",
title: t`Last name`,
placeholder: "Appleseed",
validate: validate.maxLength(100),
normalize: lastName => lastName || null,
},
];
const getEmailField = () => ({
name: "email",
title: t`Email`,
placeholder: "nicetoseeyou@email.com",
validate: validate.required().email(),
});
const getLocaleField = () => ({
name: "locale",
title: t`Language`,
type: "select",
options: [
[null, t`Use site default`],
..._.sortBy(
MetabaseSettings.get("available-locales") || [["en", "English"]],
([code, name]) => name,
),
].map(([code, name]) => ({ name, value: code })),
});
const getPasswordFields = () => [
{
name: "password",
title: t`Create a password`,
type: "password",
placeholder: t`Shhh...`,
validate: validate.required().passwordComplexity(),
},
{
name: "password_confirm",
title: t`Confirm your password`,
type: "password",
placeholder: t`Shhh... but one more time so we get it right`,
validate: (password_confirm, { values: { password } = {} }) => {
if (!password_confirm) {
return t`required`;
} else if (password_confirm !== password) {
return t`passwords do not match`;
}
},
},
];
export default {
admin: {
fields: [
...getNameFields(),
getEmailField(),
{
name: "user_group_memberships",
title: t`Groups`,
type: FormGroupsWidget,
},
...PLUGIN_ADMIN_USER_FORM_FIELDS,
],
},
user: user => {
const isSsoUser = !PLUGIN_IS_PASSWORD_USER.every(predicate =>
predicate(user),
);
const fields = isSsoUser
? [getLocaleField()]
: [...getNameFields(), getEmailField(), getLocaleField()];
return {
fields,
disablePristineSubmit: true,
};
},
setup_invite: user => ({
fields: [
...getNameFields(),
{
name: "email",
title: t`Email`,
placeholder: "nicetoseeyou@email.com",
validate: email => {
if (!email) {
return t`required`;
} else if (!MetabaseUtils.isEmail(email)) {
return t`must be a valid email address`;
} else if (email === user.email) {
return t`must be different from the email address you used in setup`;
}
},
name: "first_name",
title: t`First name`,
placeholder: "Johnny",
autoFocus: true,
validate: validate.maxLength(100),
normalize: firstName => firstName || null,
},
],
}),
password: {
fields: [
{
name: "old_password",
type: "password",
title: t`Current password`,
placeholder: t`Shhh...`,
validate: validate.required(),
name: "last_name",
title: t`Last name`,
placeholder: "Appleseed",
validate: validate.maxLength(100),
normalize: lastName => lastName || null,
},
...getPasswordFields(),
],
},
newsletter: {
fields: [
{
name: "email",
title: t`Email`,
placeholder: "nicetoseeyou@email.com",
autoFocus: true,
validate: validate.required().email(),
},
{
name: "user_group_memberships",
title: t`Groups`,
type: FormGroupsWidget,
},
...PLUGIN_ADMIN_USER_FORM_FIELDS,
],
},
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment