Merge branch 'edit-profile-fixes' into 'develop'
Edit profile fixes Closes #1018 See merge request soapbox-pub/soapbox-fe!1597
This commit is contained in:
commit
d222eb2a0c
|
@ -6,7 +6,7 @@ import api from '../api';
|
||||||
import { loadCredentials } from './auth';
|
import { loadCredentials } from './auth';
|
||||||
import { importFetchedAccount } from './importer';
|
import { importFetchedAccount } from './importer';
|
||||||
|
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError, AxiosRequestHeaders } from 'axios';
|
||||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||||
import type { APIEntity } from 'soapbox/types/entities';
|
import type { APIEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
@ -62,12 +62,16 @@ const persistAuthAccount = (account: APIEntity, params: Record<string, any>) =>
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const patchMe = (params: Record<string, any>) =>
|
const patchMe = (params: Record<string, any>, isFormData = false) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
dispatch(patchMeRequest());
|
dispatch(patchMeRequest());
|
||||||
|
|
||||||
|
const headers: AxiosRequestHeaders = isFormData ? {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
} : {};
|
||||||
|
|
||||||
return api(getState)
|
return api(getState)
|
||||||
.patch('/api/v1/accounts/update_credentials', params)
|
.patch('/api/v1/accounts/update_credentials', params, { headers })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
persistAuthAccount(response.data, params);
|
persistAuthAccount(response.data, params);
|
||||||
dispatch(patchMeSuccess(response.data));
|
dispatch(patchMeSuccess(response.data));
|
||||||
|
|
|
@ -4,9 +4,19 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
import { updateNotificationSettings } from 'soapbox/actions/accounts';
|
import { updateNotificationSettings } from 'soapbox/actions/accounts';
|
||||||
import { patchMe } from 'soapbox/actions/me';
|
import { patchMe } from 'soapbox/actions/me';
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import BirthdayInput from 'soapbox/components/birthday_input';
|
|
||||||
import List, { ListItem } from 'soapbox/components/list';
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
import { Button, Column, Form, FormActions, FormGroup, Input, Textarea, HStack, Toggle, FileInput } from 'soapbox/components/ui';
|
import {
|
||||||
|
Button,
|
||||||
|
Column,
|
||||||
|
FileInput,
|
||||||
|
Form,
|
||||||
|
FormActions,
|
||||||
|
FormGroup,
|
||||||
|
HStack,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
Toggle,
|
||||||
|
} from 'soapbox/components/ui';
|
||||||
import Streamfield, { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield';
|
import Streamfield, { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield';
|
||||||
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
import { useAppSelector, useAppDispatch, useOwnAccount, useFeatures } from 'soapbox/hooks';
|
||||||
import { normalizeAccount } from 'soapbox/normalizers';
|
import { normalizeAccount } from 'soapbox/normalizers';
|
||||||
|
@ -25,25 +35,6 @@ const hidesNetwork = (account: Account): boolean => {
|
||||||
return Boolean(hide_followers && hide_follows && hide_followers_count && hide_follows_count);
|
return Boolean(hide_followers && hide_follows && hide_followers_count && hide_follows_count);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Converts JSON objects to FormData. */
|
|
||||||
// https://stackoverflow.com/a/60286175/8811886
|
|
||||||
// @ts-ignore
|
|
||||||
const toFormData = (f => f(f))(h => f => f(x => h(h)(f)(x)))(f => fd => pk => d => {
|
|
||||||
if (d instanceof Object) {
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
Object.keys(d).forEach(k => {
|
|
||||||
const v = d[k];
|
|
||||||
if (pk) k = `${pk}[${k}]`;
|
|
||||||
if (v instanceof Object && !(v instanceof Date) && !(v instanceof File)) {
|
|
||||||
return f(fd)(k)(v);
|
|
||||||
} else {
|
|
||||||
fd.append(k, v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return fd;
|
|
||||||
})(new FormData())();
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.edit_profile', defaultMessage: 'Edit profile' },
|
heading: { id: 'column.edit_profile', defaultMessage: 'Edit profile' },
|
||||||
header: { id: 'edit_profile.header', defaultMessage: 'Edit Profile' },
|
header: { id: 'edit_profile.header', defaultMessage: 'Edit Profile' },
|
||||||
|
@ -205,9 +196,8 @@ const EditProfile: React.FC = () => {
|
||||||
|
|
||||||
const handleSubmit: React.FormEventHandler = (event) => {
|
const handleSubmit: React.FormEventHandler = (event) => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
const formData = toFormData(data);
|
|
||||||
|
|
||||||
promises.push(dispatch(patchMe(formData)));
|
promises.push(dispatch(patchMe(data, true)));
|
||||||
|
|
||||||
if (features.muteStrangers) {
|
if (features.muteStrangers) {
|
||||||
promises.push(
|
promises.push(
|
||||||
|
@ -242,10 +232,6 @@ const EditProfile: React.FC = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBirthdayChange = (date: string) => {
|
|
||||||
updateData('birthday', date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHideNetworkChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
const handleHideNetworkChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||||
const hide = e.target.checked;
|
const hide = e.target.checked;
|
||||||
|
|
||||||
|
@ -329,9 +315,12 @@ const EditProfile: React.FC = () => {
|
||||||
<FormGroup
|
<FormGroup
|
||||||
labelText={<FormattedMessage id='edit_profile.fields.birthday_label' defaultMessage='Birthday' />}
|
labelText={<FormattedMessage id='edit_profile.fields.birthday_label' defaultMessage='Birthday' />}
|
||||||
>
|
>
|
||||||
<BirthdayInput
|
<Input
|
||||||
|
type='text'
|
||||||
value={data.birthday}
|
value={data.birthday}
|
||||||
onChange={handleBirthdayChange}
|
onChange={handleTextChange('birthday')}
|
||||||
|
placeholder='YYYY-MM-DD'
|
||||||
|
pattern='\d{4}-\d{2}-\d{2}'
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -47,6 +47,13 @@ describe('normalizeAccount()', () => {
|
||||||
expect(result.birthday).toEqual('1993-07-03');
|
expect(result.birthday).toEqual('1993-07-03');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('normalizes undefined birthday to empty string', () => {
|
||||||
|
const account = require('soapbox/__fixtures__/mastodon-account.json');
|
||||||
|
const result = normalizeAccount(account);
|
||||||
|
|
||||||
|
expect(result.birthday).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
it('normalizes Pleroma legacy fields', () => {
|
it('normalizes Pleroma legacy fields', () => {
|
||||||
const account = require('soapbox/__fixtures__/pleroma-2.2.2-account.json');
|
const account = require('soapbox/__fixtures__/pleroma-2.2.2-account.json');
|
||||||
const result = normalizeAccount(account);
|
const result = normalizeAccount(account);
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const AccountRecord = ImmutableRecord({
|
||||||
acct: '',
|
acct: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
avatar_static: '',
|
avatar_static: '',
|
||||||
birthday: undefined as string | undefined,
|
birthday: '',
|
||||||
bot: false,
|
bot: false,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
discoverable: false,
|
discoverable: false,
|
||||||
|
@ -261,6 +261,12 @@ const normalizeDiscoverable = (account: ImmutableMap<string, any>) => {
|
||||||
return account.set('discoverable', discoverable);
|
return account.set('discoverable', discoverable);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Normalize undefined/null birthday to empty string. */
|
||||||
|
const fixBirthday = (account: ImmutableMap<string, any>) => {
|
||||||
|
const birthday = account.get('birthday');
|
||||||
|
return account.set('birthday', birthday || '');
|
||||||
|
};
|
||||||
|
|
||||||
export const normalizeAccount = (account: Record<string, any>) => {
|
export const normalizeAccount = (account: Record<string, any>) => {
|
||||||
return AccountRecord(
|
return AccountRecord(
|
||||||
ImmutableMap(fromJS(account)).withMutations(account => {
|
ImmutableMap(fromJS(account)).withMutations(account => {
|
||||||
|
@ -280,6 +286,7 @@ export const normalizeAccount = (account: Record<string, any>) => {
|
||||||
addStaffFields(account);
|
addStaffFields(account);
|
||||||
fixUsername(account);
|
fixUsername(account);
|
||||||
fixDisplayName(account);
|
fixDisplayName(account);
|
||||||
|
fixBirthday(account);
|
||||||
addInternalFields(account);
|
addInternalFields(account);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -148,9 +148,7 @@ const getInstanceFeatures = (instance: Instance) => {
|
||||||
* @see POST /api/v1/accounts
|
* @see POST /api/v1/accounts
|
||||||
* @see PATCH /api/v1/accounts/update_credentials
|
* @see PATCH /api/v1/accounts/update_credentials
|
||||||
*/
|
*/
|
||||||
// birthdays: v.software === PLEROMA && gte(v.version, '2.4.50'),
|
birthdays: v.software === PLEROMA && v.build === SOAPBOX && gte(v.version, '2.4.50'),
|
||||||
// FIXME: temporarily disabled until they can be deleted on the backend.
|
|
||||||
birthdays: false,
|
|
||||||
|
|
||||||
/** Whether people who blocked you are visible through the API. */
|
/** Whether people who blocked you are visible through the API. */
|
||||||
blockersVisible: features.includes('blockers_visible'),
|
blockersVisible: features.includes('blockers_visible'),
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"array-includes": "^3.1.5",
|
"array-includes": "^3.1.5",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
"axios": "^0.27.2",
|
"axios": "^1.0.0-alpha.1",
|
||||||
"axios-mock-adapter": "^1.21.1",
|
"axios-mock-adapter": "^1.21.1",
|
||||||
"babel-loader": "^8.2.5",
|
"babel-loader": "^8.2.5",
|
||||||
"babel-plugin-lodash": "^3.3.4",
|
"babel-plugin-lodash": "^3.3.4",
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -3506,13 +3506,14 @@ axios-mock-adapter@^1.21.1:
|
||||||
fast-deep-equal "^3.1.3"
|
fast-deep-equal "^3.1.3"
|
||||||
is-buffer "^2.0.5"
|
is-buffer "^2.0.5"
|
||||||
|
|
||||||
axios@^0.27.2:
|
axios@^1.0.0-alpha.1:
|
||||||
version "0.27.2"
|
version "1.0.0-alpha.1"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.0.0-alpha.1.tgz#ce69c17ca7605d01787ca754dd906e6fccdf71ee"
|
||||||
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
|
integrity sha512-p+meG161943WT+K7sJYquHR46xxi/z0tk7vnSmEf/LrfEAyiP+0uTMMYk1OEo1IRF18oGRhnFxN1y8fLcXaTMw==
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.14.9"
|
follow-redirects "^1.15.0"
|
||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
axobject-query@^2.2.0:
|
axobject-query@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
|
@ -5827,10 +5828,10 @@ follow-redirects@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||||
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||||
|
|
||||||
follow-redirects@^1.14.9:
|
follow-redirects@^1.15.0:
|
||||||
version "1.14.9"
|
version "1.15.1"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
|
||||||
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
|
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
|
||||||
|
|
||||||
foreach@^2.0.5:
|
foreach@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
|
@ -9501,6 +9502,11 @@ proxy-addr@~2.0.7:
|
||||||
forwarded "0.2.0"
|
forwarded "0.2.0"
|
||||||
ipaddr.js "1.9.1"
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
proxy-from-env@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
|
||||||
psl@^1.1.33:
|
psl@^1.1.33:
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
|
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
|
||||||
|
|
Loading…
Reference in New Issue