1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

using formik on profile settings

This commit is contained in:
Fedor Katurov 2022-01-08 18:37:16 +07:00
parent 3c0571816c
commit e8f0ec1f36
5 changed files with 106 additions and 75 deletions

View file

@ -1,4 +1,4 @@
import React, { FC, useCallback, useEffect, useState } from 'react'; import React, { FC } from 'react';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
import { Textarea } from '~/components/input/Textarea'; import { Textarea } from '~/components/input/Textarea';
import { Button } from '~/components/input/Button'; import { Button } from '~/components/input/Button';
@ -10,62 +10,22 @@ import { ProfileAccounts } from '~/components/profile/ProfileAccounts';
import classNames from 'classnames'; import classNames from 'classnames';
import { useUser } from '~/hooks/user/userUser'; import { useUser } from '~/hooks/user/userUser';
import { useProfileContext } from '~/utils/providers/ProfileProvider'; import { useProfileContext } from '~/utils/providers/ProfileProvider';
import { showErrorToast } from '~/utils/errors/showToast'; import { useProfileForm } from '~/hooks/profile/useProfileForm';
import { getValidationErrors } from '~/utils/errors/getValidationErrors'; import { has } from 'ramda';
import { showToastSuccess } from '~/utils/toast';
import { getRandomPhrase } from '~/constants/phrases'; const getError = (error?: string) => (error && has(error, ERROR_LITERAL) ? error : undefined);
const ProfileSettings: FC = () => { const ProfileSettings: FC = () => {
const [errors, setErrors] = useState<Record<string, any>>({});
const user = useUser(); const user = useUser();
const { updateProfile } = useProfileContext(); const { updateProfile } = useProfileContext();
const [password, setPassword] = useState(''); const { handleSubmit, values, errors, handleChange } = useProfileForm(
const [new_password, setNewPassword] = useState(''); { ...user, password: '', newPassword: '' },
updateProfile
const [data, setData] = useState(user);
const setDescription = useCallback(description => setData({ ...data, description }), [
data,
setData,
]);
const setEmail = useCallback(email => setData({ ...data, email }), [data, setData]);
const setUsername = useCallback(username => setData({ ...data, username }), [data, setData]);
const setFullname = useCallback(fullname => setData({ ...data, fullname }), [data, setData]);
const onSubmit = useCallback(
async event => {
try {
event.preventDefault();
const fields = {
...data,
password: password.length > 0 && password ? password : undefined,
new_password: new_password.length > 0 && new_password ? new_password : undefined,
};
await updateProfile(fields);
showToastSuccess(getRandomPhrase('SUCCESS'));
} catch (error) {
showErrorToast(error);
const validationErrors = getValidationErrors(error);
if (validationErrors) {
setErrors(validationErrors);
}
}
},
[data, password, new_password, updateProfile]
); );
useEffect(() => {
setErrors({});
}, [password, new_password, data]);
return ( return (
<form className={styles.wrap} onSubmit={onSubmit}> <form className={styles.wrap} onSubmit={handleSubmit}>
<Group> <Group>
<Group className={styles.pad}> <Group className={styles.pad}>
<div className={styles.pad__title}> <div className={styles.pad__title}>
@ -73,13 +33,17 @@ const ProfileSettings: FC = () => {
</div> </div>
<InputText <InputText
value={data.fullname} value={values.fullname}
handler={setFullname} handler={handleChange('fullname')}
title="Полное имя" title="Полное имя"
error={errors.fullname && ERROR_LITERAL[errors.fullname]} error={getError(errors.fullname)}
/> />
<Textarea value={data.description} handler={setDescription} title="Описание" /> <Textarea
value={values.description}
handler={handleChange('description')}
title="Описание"
/>
<div className={styles.small}> <div className={styles.small}>
Описание будет видно на странице профиля. Здесь работают те же правила оформления, что и Описание будет видно на странице профиля. Здесь работают те же правила оформления, что и
@ -95,33 +59,33 @@ const ProfileSettings: FC = () => {
</div> </div>
<InputText <InputText
value={data.username} value={values.username}
handler={setUsername} handler={handleChange('username')}
title="Логин" title="Логин"
error={errors.username && ERROR_LITERAL[errors.username]} error={getError(errors.username)}
/> />
<InputText <InputText
value={data.email} value={values.email}
handler={setEmail} handler={handleChange('email')}
title="E-mail" title="E-mail"
error={errors.email && ERROR_LITERAL[errors.email]} error={getError(errors.email)}
/> />
<InputText <InputText
value={new_password} value={values.newPassword}
handler={setNewPassword} handler={handleChange('newPassword')}
title="Новый пароль" title="Новый пароль"
type="password" type="password"
error={errors.new_password && ERROR_LITERAL[errors.new_password]} error={getError(errors.newPassword)}
/> />
<InputText <InputText
value={password} value={values.password}
handler={setPassword} handler={handleChange('password')}
title="Старый пароль" title="Старый пароль"
type="password" type="password"
error={errors.password && ERROR_LITERAL[errors.password]} error={getError(errors.password)}
/> />
<div className={styles.small}> <div className={styles.small}>

View file

@ -2,17 +2,20 @@ import { useUploader } from '~/hooks/data/useUploader';
import { UploadSubject, UploadTarget } from '~/constants/uploads'; import { UploadSubject, UploadTarget } from '~/constants/uploads';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { showErrorToast } from '~/utils/errors/showToast'; import { showErrorToast } from '~/utils/errors/showToast';
import { IUser } from '~/redux/auth/types'; import { ApiUpdateUserRequest, IUser } from '~/redux/auth/types';
import { apiUpdateUser } from '~/redux/auth/api'; import { apiUpdateUser } from '~/redux/auth/api';
export const usePatchProfile = (updateUserData: (user: Partial<IUser>) => void) => { export const usePatchProfile = (updateUserData: (user: Partial<IUser>) => void) => {
const { uploadFile } = useUploader(UploadSubject.Avatar, UploadTarget.Profiles); const { uploadFile } = useUploader(UploadSubject.Avatar, UploadTarget.Profiles);
const updateProfile = useCallback(async (user: Partial<IUser>) => { const updateProfile = useCallback(
const result = await apiUpdateUser({ user }); async (user: Partial<ApiUpdateUserRequest['user']>) => {
await updateUserData(result.user); const result = await apiUpdateUser({ user });
return result.user; await updateUserData(result.user);
}, []); return result.user;
},
[updateUserData]
);
const updatePhoto = useCallback( const updatePhoto = useCallback(
async (file: File) => { async (file: File) => {
@ -23,7 +26,7 @@ export const usePatchProfile = (updateUserData: (user: Partial<IUser>) => void)
showErrorToast(error); showErrorToast(error);
} }
}, },
[updateUserData, uploadFile, updateProfile] [uploadFile, updateProfile]
); );
return { updatePhoto, updateProfile }; return { updatePhoto, updateProfile };

View file

@ -0,0 +1,62 @@
import { IUser } from '~/redux/auth/types';
import { Asserts, object, string } from 'yup';
import { ERRORS } from '~/constants/errors';
import { FormikConfig, useFormik } from 'formik';
import { useCallback, useRef } from 'react';
import { showToastSuccess } from '~/utils/toast';
import { getRandomPhrase } from '~/constants/phrases';
import { showErrorToast } from '~/utils/errors/showToast';
import { getValidationErrors } from '~/utils/errors/getValidationErrors';
const validationSchema = object({
username: string()
.default('')
.required(ERRORS.REQUIRED),
fullname: string().default(''),
newPassword: string().optional(),
description: string().default(''),
email: string()
.default('')
.email(ERRORS.NOT_AN_EMAIL),
password: string().optional(),
});
export type ProfileFormData = Asserts<typeof validationSchema>;
export const useProfileForm = (
values: ProfileFormData,
submitter: (data: ProfileFormData) => Promise<IUser>
) => {
const initialValues = useRef(values).current;
const onSubmit = useCallback<FormikConfig<ProfileFormData>['onSubmit']>(
async (values, { setErrors, setValues }) => {
try {
const fields = {
...values,
password: values.password?.length ? values.password : undefined,
new_password: values.newPassword?.length ? values.newPassword : undefined,
};
const result = await submitter(fields);
setValues({ ...result, password: '', newPassword: '' });
showToastSuccess(getRandomPhrase('SUCCESS'));
} catch (error) {
showErrorToast(error);
const validationErrors = getValidationErrors(error);
if (validationErrors) {
setErrors(validationErrors);
}
}
},
[submitter]
);
return useFormik({
initialValues,
onSubmit,
validationSchema,
});
};

View file

@ -91,7 +91,9 @@ export type ApiUserLoginResult = { token: string; user: IUser };
export type ApiAuthGetUserRequest = {}; export type ApiAuthGetUserRequest = {};
export type ApiAuthGetUserResult = { user: IUser }; export type ApiAuthGetUserResult = { user: IUser };
export type ApiUpdateUserRequest = { user: Partial<IUser> }; export type ApiUpdateUserRequest = {
user: Partial<IUser & { password: string; newPassword: string }>;
};
export type ApiUpdateUserResult = { user: IUser; errors: Record<Partial<keyof IUser>, string> }; export type ApiUpdateUserResult = { user: IUser; errors: Record<Partial<keyof IUser>, string> };
export type ApiAuthGetUserProfileRequest = { username: string }; export type ApiAuthGetUserProfileRequest = { username: string };

View file

@ -1,5 +1,5 @@
import { createContext, FC, useCallback, useContext } from "react"; import { createContext, FC, useCallback, useContext } from "react";
import { IUser } from "~/redux/auth/types"; import { ApiUpdateUserRequest, IUser } from "~/redux/auth/types";
import { useGetProfile } from "~/hooks/profile/useGetProfile"; import { useGetProfile } from "~/hooks/profile/useGetProfile";
import { EMPTY_USER } from "~/redux/auth/constants"; import { EMPTY_USER } from "~/redux/auth/constants";
import { usePatchProfile } from "~/hooks/profile/usePatchProfile"; import { usePatchProfile } from "~/hooks/profile/usePatchProfile";
@ -15,7 +15,7 @@ interface ProfileContextValue {
profile: IUser; profile: IUser;
isLoading: boolean; isLoading: boolean;
updatePhoto: (file: File) => Promise<unknown>; updatePhoto: (file: File) => Promise<unknown>;
updateProfile: (user: Partial<IUser>) => Promise<IUser>; updateProfile: (user: Partial<ApiUpdateUserRequest['user']>) => Promise<IUser>;
} }
const ProfileContext = createContext<ProfileContextValue>({ const ProfileContext = createContext<ProfileContextValue>({