2022-09-29 12:02:41 +02:00
|
|
|
/*
|
|
|
|
|
GoToSocial
|
2023-03-12 18:49:06 +01:00
|
|
|
Copyright (C) GoToSocial Authors admin@gotosocial.org
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
2022-09-29 12:02:41 +02:00
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-04-05 15:25:21 +02:00
|
|
|
import React, { useMemo, useState } from "react";
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2024-01-16 18:28:56 +01:00
|
|
|
import {
|
2023-01-18 14:45:14 +01:00
|
|
|
useTextInput,
|
|
|
|
|
useFileInput,
|
2023-06-13 12:21:26 +02:00
|
|
|
useBoolInput,
|
2024-03-25 18:32:24 +01:00
|
|
|
useFieldArrayInput,
|
2025-04-07 16:14:41 +02:00
|
|
|
} from "../../../lib/form";
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
import useFormSubmit from "../../../lib/form/submit";
|
|
|
|
|
import { useWithFormContext, FormContext } from "../../../lib/form/context";
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2024-01-16 18:28:56 +01:00
|
|
|
import {
|
2022-09-29 12:02:41 +02:00
|
|
|
TextInput,
|
|
|
|
|
TextArea,
|
2023-01-18 14:45:14 +01:00
|
|
|
FileInput,
|
2024-03-25 18:32:24 +01:00
|
|
|
Checkbox,
|
2024-08-27 12:16:45 +02:00
|
|
|
Select
|
2025-04-07 16:14:41 +02:00
|
|
|
} from "../../../components/form/inputs";
|
2023-01-18 14:45:14 +01:00
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
import FormWithData from "../../../lib/form/form-with-data";
|
|
|
|
|
import FakeProfile from "../../../components/profile";
|
|
|
|
|
import MutationButton from "../../../components/form/mutation-button";
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
import {
|
|
|
|
|
useAccountThemesQuery,
|
|
|
|
|
useDeleteAvatarMutation,
|
|
|
|
|
useDeleteHeaderMutation,
|
|
|
|
|
} from "../../../lib/query/user";
|
|
|
|
|
import { useUpdateCredentialsMutation } from "../../../lib/query/user";
|
|
|
|
|
import { useVerifyCredentialsQuery } from "../../../lib/query/login";
|
|
|
|
|
import { useInstanceV1Query } from "../../../lib/query/gts-api";
|
|
|
|
|
import { Account } from "../../../lib/types/account";
|
2023-10-17 12:46:06 +02:00
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
export default function Profile() {
|
2023-01-18 14:45:14 +01:00
|
|
|
return (
|
|
|
|
|
<FormWithData
|
2023-10-17 12:46:06 +02:00
|
|
|
dataQuery={useVerifyCredentialsQuery}
|
2025-04-07 16:14:41 +02:00
|
|
|
DataForm={ProfileForm}
|
2023-01-18 14:45:14 +01:00
|
|
|
/>
|
|
|
|
|
);
|
2024-01-16 18:28:56 +01:00
|
|
|
}
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
interface ProfileFormProps {
|
2024-10-21 14:04:50 +02:00
|
|
|
data: Account;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-07 16:14:41 +02:00
|
|
|
function ProfileForm({ data: profile }: ProfileFormProps) {
|
2023-10-17 12:46:06 +02:00
|
|
|
const { data: instance } = useInstanceV1Query();
|
2023-06-13 12:21:26 +02:00
|
|
|
const instanceConfig = React.useMemo(() => {
|
|
|
|
|
return {
|
|
|
|
|
allowCustomCSS: instance?.configuration?.accounts?.allow_custom_css === true,
|
|
|
|
|
maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6
|
|
|
|
|
};
|
2023-01-18 14:45:14 +01:00
|
|
|
}, [instance]);
|
2024-03-25 18:32:24 +01:00
|
|
|
|
|
|
|
|
// Parse out available theme options into nice format.
|
|
|
|
|
const { data: themes } = useAccountThemesQuery();
|
2024-08-27 12:16:45 +02:00
|
|
|
const themeOptions = useMemo(() => {
|
|
|
|
|
let themeOptions = [
|
|
|
|
|
<option key="" value="">
|
|
|
|
|
Default
|
|
|
|
|
</option>
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
themes?.forEach((theme) => {
|
|
|
|
|
const value = theme.file_name;
|
|
|
|
|
let text = theme.title;
|
|
|
|
|
if (theme.description) {
|
|
|
|
|
text += " - " + theme.description;
|
|
|
|
|
}
|
|
|
|
|
themeOptions.push(
|
|
|
|
|
<option key={value} value={value}>
|
|
|
|
|
{text}
|
|
|
|
|
</option>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return themeOptions;
|
|
|
|
|
}, [themes]);
|
2022-09-29 12:02:41 +02:00
|
|
|
|
2023-01-18 14:45:14 +01:00
|
|
|
const form = {
|
|
|
|
|
avatar: useFileInput("avatar", { withPreview: true }),
|
2024-07-08 15:47:03 +02:00
|
|
|
avatarDescription: useTextInput("avatar_description", { source: profile }),
|
2023-01-18 14:45:14 +01:00
|
|
|
header: useFileInput("header", { withPreview: true }),
|
2024-07-08 15:47:03 +02:00
|
|
|
headerDescription: useTextInput("header_description", { source: profile }),
|
2023-02-06 09:19:56 +01:00
|
|
|
displayName: useTextInput("display_name", { source: profile }),
|
|
|
|
|
note: useTextInput("note", { source: profile, valueSelector: (p) => p.source?.note }),
|
|
|
|
|
bot: useBoolInput("bot", { source: profile }),
|
|
|
|
|
locked: useBoolInput("locked", { source: profile }),
|
2023-09-29 13:01:36 +02:00
|
|
|
discoverable: useBoolInput("discoverable", { source: profile}),
|
2023-02-06 09:19:56 +01:00
|
|
|
enableRSS: useBoolInput("enable_rss", { source: profile }),
|
2024-04-02 11:42:24 +02:00
|
|
|
hideCollections: useBoolInput("hide_collections", { source: profile }),
|
2025-03-26 16:59:39 +01:00
|
|
|
webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p: Account) => p.source?.web_visibility }),
|
|
|
|
|
webLayout: useTextInput("web_layout", { source: profile, valueSelector: (p: Account) => p.source?.web_layout }),
|
2023-06-13 12:21:26 +02:00
|
|
|
fields: useFieldArrayInput("fields_attributes", {
|
|
|
|
|
defaultValue: profile?.source?.fields,
|
|
|
|
|
length: instanceConfig.maxPinnedFields
|
|
|
|
|
}),
|
2024-03-25 18:32:24 +01:00
|
|
|
customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }),
|
2024-08-27 12:16:45 +02:00
|
|
|
theme: useTextInput("theme", { source: profile }),
|
2023-01-18 14:45:14 +01:00
|
|
|
};
|
|
|
|
|
|
2025-04-05 15:25:21 +02:00
|
|
|
const [ noHeader, setNoHeader ] = useState(!profile.header_media_id);
|
|
|
|
|
const [ deleteHeader, deleteHeaderRes ] = useDeleteHeaderMutation();
|
|
|
|
|
const [ noAvatar, setNoAvatar ] = useState(!profile.avatar_media_id);
|
|
|
|
|
const [ deleteAvatar, deleteAvatarRes ] = useDeleteAvatarMutation();
|
|
|
|
|
|
2023-10-17 12:46:06 +02:00
|
|
|
const [submitForm, result] = useFormSubmit(form, useUpdateCredentialsMutation(), {
|
2024-01-16 18:28:56 +01:00
|
|
|
changedOnly: true,
|
2025-04-05 15:25:21 +02:00
|
|
|
onFinish: (res) => {
|
|
|
|
|
if ('data' in res) {
|
|
|
|
|
form.avatar.reset();
|
|
|
|
|
form.header.reset();
|
|
|
|
|
setNoAvatar(!res.data.avatar_media_id);
|
|
|
|
|
setNoHeader(!res.data.header_media_id);
|
|
|
|
|
}
|
2023-06-13 12:21:26 +02:00
|
|
|
}
|
|
|
|
|
});
|
2022-09-29 12:02:41 +02:00
|
|
|
|
|
|
|
|
return (
|
2023-01-18 14:45:14 +01:00
|
|
|
<form className="user-profile" onSubmit={submitForm}>
|
2025-06-23 17:29:27 +02:00
|
|
|
<div className="form-section-docs">
|
|
|
|
|
<h1>Profile</h1>
|
|
|
|
|
<p>
|
|
|
|
|
On this page you can change various settings relating to the appearance and discoverability of your profile and posts.
|
|
|
|
|
<br/>After changing settings and/or uploading a new avatar or header, be sure to scroll to the bottom of this page and click "Save profile info" to confirm your changes.
|
|
|
|
|
</p>
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.gotosocial.org/en/latest/user_guide/settings/#profile"
|
|
|
|
|
target="_blank"
|
|
|
|
|
className="docslink"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
>
|
|
|
|
|
Learn more about this settings page (opens in a new tab)
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
2022-09-29 12:02:41 +02:00
|
|
|
<div className="overview">
|
2023-01-18 14:45:14 +01:00
|
|
|
<FakeProfile
|
|
|
|
|
avatar={form.avatar.previewValue ?? profile.avatar}
|
|
|
|
|
header={form.header.previewValue ?? profile.header}
|
|
|
|
|
display_name={form.displayName.value ?? profile.username}
|
2024-07-24 10:40:56 +02:00
|
|
|
bot={profile.bot}
|
2023-01-18 14:45:14 +01:00
|
|
|
username={profile.username}
|
2023-05-13 12:17:22 +02:00
|
|
|
role={profile.role}
|
2023-01-18 14:45:14 +01:00
|
|
|
/>
|
2024-07-08 15:47:03 +02:00
|
|
|
|
2024-10-21 14:04:50 +02:00
|
|
|
<fieldset className="file-input-with-image-description">
|
|
|
|
|
<legend>Header</legend>
|
2024-07-08 15:47:03 +02:00
|
|
|
<FileInput
|
2024-10-21 14:04:50 +02:00
|
|
|
label="Upload file"
|
2024-07-08 15:47:03 +02:00
|
|
|
field={form.header}
|
|
|
|
|
accept="image/png, image/jpeg, image/webp, image/gif"
|
|
|
|
|
/>
|
|
|
|
|
<TextInput
|
|
|
|
|
field={form.headerDescription}
|
2024-10-21 14:04:50 +02:00
|
|
|
label="Image description; only settable if not using default header"
|
2024-07-08 15:47:03 +02:00
|
|
|
placeholder="A green field with pink flowers."
|
|
|
|
|
autoCapitalize="sentences"
|
2025-04-05 15:25:21 +02:00
|
|
|
disabled={noHeader && !form.header.value}
|
|
|
|
|
/>
|
|
|
|
|
<MutationButton
|
|
|
|
|
className="delete-header-button"
|
|
|
|
|
label="Delete header"
|
2025-04-09 14:14:20 +02:00
|
|
|
tabIndex={0}
|
2025-04-05 15:25:21 +02:00
|
|
|
disabled={noHeader}
|
|
|
|
|
result={deleteHeaderRes}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
deleteHeader().then(res => {
|
|
|
|
|
if ('data' in res) {
|
|
|
|
|
setNoHeader(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}}
|
2024-07-08 15:47:03 +02:00
|
|
|
/>
|
2024-10-21 14:04:50 +02:00
|
|
|
</fieldset>
|
2025-04-09 14:14:20 +02:00
|
|
|
|
2024-10-21 14:04:50 +02:00
|
|
|
<fieldset className="file-input-with-image-description">
|
|
|
|
|
<legend>Avatar</legend>
|
2024-07-08 15:47:03 +02:00
|
|
|
<FileInput
|
2024-10-21 14:04:50 +02:00
|
|
|
label="Upload file (1:1 images look best)"
|
2024-07-08 15:47:03 +02:00
|
|
|
field={form.avatar}
|
|
|
|
|
accept="image/png, image/jpeg, image/webp, image/gif"
|
|
|
|
|
/>
|
|
|
|
|
<TextInput
|
|
|
|
|
field={form.avatarDescription}
|
2024-10-21 14:04:50 +02:00
|
|
|
label="Image description; only settable if not using default avatar"
|
2024-07-08 15:47:03 +02:00
|
|
|
placeholder="A cute drawing of a smiling sloth."
|
|
|
|
|
autoCapitalize="sentences"
|
2025-04-05 15:25:21 +02:00
|
|
|
disabled={noAvatar && !form.avatar.value}
|
|
|
|
|
/>
|
|
|
|
|
<MutationButton
|
|
|
|
|
className="delete-avatar-button"
|
|
|
|
|
label="Delete avatar"
|
2025-04-09 14:14:20 +02:00
|
|
|
tabIndex={0}
|
2025-04-05 15:25:21 +02:00
|
|
|
disabled={noAvatar}
|
|
|
|
|
result={deleteAvatarRes}
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
deleteAvatar().then(res => {
|
|
|
|
|
if ('data' in res) {
|
|
|
|
|
setNoAvatar(true);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}}
|
2024-07-08 15:47:03 +02:00
|
|
|
/>
|
2024-10-21 14:04:50 +02:00
|
|
|
</fieldset>
|
2024-03-25 18:32:24 +01:00
|
|
|
|
2025-03-26 16:59:39 +01:00
|
|
|
<span>After choosing theme or layout and saving, <a href={profile.url} target="_blank">open your profile</a> and refresh to see changes.</span>
|
|
|
|
|
|
|
|
|
|
<Select
|
|
|
|
|
label="Theme for the web view of your profile"
|
|
|
|
|
field={form.theme}
|
|
|
|
|
options={<>{themeOptions}</>}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Select
|
|
|
|
|
field={form.webLayout}
|
|
|
|
|
label="Layout for the web view of your profile"
|
|
|
|
|
options={
|
|
|
|
|
<>
|
|
|
|
|
<option value="microblog">Classic microblog layout (show recent + pinned posts; media shown alongside its parent post)</option>
|
|
|
|
|
<option value="gallery">'Gram-style gallery layout (show recent + pinned media; parent posts still accessible by link)</option>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2022-09-29 12:02:41 +02:00
|
|
|
</div>
|
2023-09-29 13:01:36 +02:00
|
|
|
|
|
|
|
|
<div className="form-section-docs">
|
|
|
|
|
<h3>Basic Information</h3>
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.gotosocial.org/en/latest/user_guide/settings/#basic-information"
|
|
|
|
|
target="_blank"
|
|
|
|
|
className="docslink"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
>
|
|
|
|
|
Learn more about these settings (opens in a new tab)
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
2024-07-24 10:40:56 +02:00
|
|
|
<Checkbox
|
|
|
|
|
field={form.bot}
|
|
|
|
|
label="Mark as bot account; this indicates to other users that this is an automated account"
|
|
|
|
|
/>
|
2022-09-29 12:02:41 +02:00
|
|
|
<TextInput
|
2023-01-18 14:45:14 +01:00
|
|
|
field={form.displayName}
|
2023-09-29 13:01:36 +02:00
|
|
|
label="Display name"
|
2024-07-08 09:38:27 +02:00
|
|
|
placeholder="A GoToSocial User"
|
|
|
|
|
autoCapitalize="words"
|
|
|
|
|
spellCheck="false"
|
2022-09-29 12:02:41 +02:00
|
|
|
/>
|
|
|
|
|
<TextArea
|
2023-01-18 14:45:14 +01:00
|
|
|
field={form.note}
|
|
|
|
|
label="Bio"
|
|
|
|
|
placeholder="Just trying out GoToSocial, my pronouns are they/them and I like sloths."
|
2024-07-08 09:38:27 +02:00
|
|
|
autoCapitalize="sentences"
|
2023-01-18 14:45:14 +01:00
|
|
|
rows={8}
|
2022-09-29 12:02:41 +02:00
|
|
|
/>
|
2024-07-24 10:40:56 +02:00
|
|
|
<fieldset>
|
|
|
|
|
<legend>Profile fields</legend>
|
|
|
|
|
<ProfileFields
|
|
|
|
|
field={form.fields}
|
|
|
|
|
/>
|
|
|
|
|
</fieldset>
|
2023-09-29 13:01:36 +02:00
|
|
|
|
|
|
|
|
<div className="form-section-docs">
|
|
|
|
|
<h3>Visibility and privacy</h3>
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.gotosocial.org/en/latest/user_guide/settings/#visibility-and-privacy"
|
|
|
|
|
target="_blank"
|
|
|
|
|
className="docslink"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
>
|
|
|
|
|
Learn more about these settings (opens in a new tab)
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
2024-09-09 18:07:25 +02:00
|
|
|
<Select
|
|
|
|
|
field={form.webVisibility}
|
[feature] Use `hidesToPublicFromUnauthedWeb` and `hidesCcPublicFromUnauthedWeb` properties for web visibility of statuses (#4315)
This pull request implements two new properties on ActivityPub actors: `hidesToPublicFromUnauthedWeb` and `hidesCcPublicFromUnauthedWeb`.
As documented, these properties allow actors to signal their preference for whether or not their posts should be hidden from unauthenticated web views (ie., web pages like the GtS frontend, web apps like the Mastodon frontend, web APIs like the Mastodon public timeline API, etc). This allows remote accounts to *opt in* to having their unlisted visibility posts shown in (for example) the replies section of the web view of a GtS thread. In future, we can also use these properties to determine whether we should show boosts of a remote actor's post on a GtS profile, and that sort of thing.
In keeping with our stance around privacy by default, GtS assumes `true` for `hidesCcPublicFromUnauthedWeb` if the property is not set on a remote actor, ie., hide unlisted/unlocked posts by default. `hidesToPublicFromUnauthedWeb` is assumed to be `false` if the property is not set on a remote actor, ie., show public posts by default.
~~WIP as I still want to work on the documentation for this a bit.~~
New props are already in the namespace document: https://gotosocial.org/ns
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4315
Reviewed-by: kim <gruf@noreply.codeberg.org>
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-07-09 16:50:25 +02:00
|
|
|
label="Visibility level of posts to show on your profile web page, and in your RSS feed (if enabled)."
|
2024-09-09 18:07:25 +02:00
|
|
|
options={
|
|
|
|
|
<>
|
|
|
|
|
<option value="public">Show Public posts only (the GoToSocial default)</option>
|
|
|
|
|
<option value="unlisted">Show Public and Unlisted posts (the Mastodon default)</option>
|
|
|
|
|
<option value="none">Show no posts</option>
|
|
|
|
|
</>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2022-09-29 12:02:41 +02:00
|
|
|
<Checkbox
|
2023-01-18 14:45:14 +01:00
|
|
|
field={form.locked}
|
2024-09-09 18:07:25 +02:00
|
|
|
label="Manually approve follow requests."
|
2022-10-08 14:00:39 +02:00
|
|
|
/>
|
2023-09-29 13:01:36 +02:00
|
|
|
<Checkbox
|
|
|
|
|
field={form.discoverable}
|
2024-09-09 18:07:25 +02:00
|
|
|
label="Mark account as discoverable by search engines and directories."
|
2023-09-29 13:01:36 +02:00
|
|
|
/>
|
2022-10-08 14:00:39 +02:00
|
|
|
<Checkbox
|
2023-01-18 14:45:14 +01:00
|
|
|
field={form.enableRSS}
|
2024-09-09 18:07:25 +02:00
|
|
|
label="Enable RSS feed of posts."
|
2022-09-29 12:02:41 +02:00
|
|
|
/>
|
2024-04-02 11:42:24 +02:00
|
|
|
<Checkbox
|
|
|
|
|
field={form.hideCollections}
|
2024-09-09 18:07:25 +02:00
|
|
|
label="Hide who you follow / are followed by."
|
2024-04-02 11:42:24 +02:00
|
|
|
/>
|
2023-09-29 13:01:36 +02:00
|
|
|
|
|
|
|
|
<div className="form-section-docs">
|
|
|
|
|
<h3>Advanced</h3>
|
|
|
|
|
<a
|
|
|
|
|
href="https://docs.gotosocial.org/en/latest/user_guide/settings/#advanced"
|
|
|
|
|
target="_blank"
|
|
|
|
|
className="docslink"
|
|
|
|
|
rel="noreferrer"
|
2022-09-29 12:02:41 +02:00
|
|
|
>
|
2023-09-29 13:01:36 +02:00
|
|
|
Learn more about these settings (opens in a new tab)
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
<TextArea
|
|
|
|
|
field={form.customCSS}
|
2023-10-24 10:28:59 +02:00
|
|
|
label={`Custom CSS` + (!instanceConfig.allowCustomCSS ? ` (not enabled on this instance)` : ``)}
|
2023-09-29 13:01:36 +02:00
|
|
|
className="monospace"
|
|
|
|
|
rows={8}
|
|
|
|
|
disabled={!instanceConfig.allowCustomCSS}
|
2024-07-08 09:38:27 +02:00
|
|
|
autoCapitalize="none"
|
|
|
|
|
spellCheck="false"
|
2023-09-29 13:01:36 +02:00
|
|
|
/>
|
2024-01-16 18:28:56 +01:00
|
|
|
<MutationButton
|
|
|
|
|
disabled={false}
|
|
|
|
|
label="Save profile info"
|
|
|
|
|
result={result}
|
|
|
|
|
/>
|
2023-01-18 14:45:14 +01:00
|
|
|
</form>
|
2022-09-29 12:02:41 +02:00
|
|
|
);
|
2023-06-13 12:21:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ProfileFields({ field: formField }) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="fields">
|
|
|
|
|
<FormContext.Provider value={formField.ctx}>
|
|
|
|
|
{formField.value.map((data, i) => (
|
|
|
|
|
<Field
|
|
|
|
|
key={i}
|
|
|
|
|
index={i}
|
|
|
|
|
data={data}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</FormContext.Provider>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Field({ index, data }) {
|
|
|
|
|
const form = useWithFormContext(index, {
|
|
|
|
|
name: useTextInput("name", { defaultValue: data.name }),
|
|
|
|
|
value: useTextInput("value", { defaultValue: data.value })
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="entry">
|
|
|
|
|
<TextInput
|
|
|
|
|
field={form.name}
|
|
|
|
|
placeholder="Name"
|
2024-07-08 09:38:27 +02:00
|
|
|
autoCapitalize="none"
|
|
|
|
|
spellCheck="false"
|
2023-06-13 12:21:26 +02:00
|
|
|
/>
|
|
|
|
|
<TextInput
|
|
|
|
|
field={form.value}
|
|
|
|
|
placeholder="Value"
|
2024-07-08 09:38:27 +02:00
|
|
|
autoCapitalize="none"
|
|
|
|
|
spellCheck="false"
|
2023-06-13 12:21:26 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2024-04-24 12:12:47 +02:00
|
|
|
}
|