mirror of
https://github.com/outline/outline.git
synced 2025-12-30 07:19:52 -06:00
Update Switch component onChange handler to use boolean callback (#9385)
* Update Switch component onChange handler to use boolean callback - Remove synthetic event handling from Switch component - Update onChange signature to (checked: boolean) => void - Update all call points across the codebase: - PublicAccess.tsx: Updated handlers for indexing, showLastModified, and published switches - Security.tsx: Created individual handlers for all security preferences - Preferences.tsx: Created individual handlers for user preferences - CollectionForm.tsx: Added createSwitchRegister helper for react-hook-form compatibility - TemplatizeDialog: Updated publish handler - DocumentMenu.tsx: Added handlers for embeds and full-width toggles - Search.tsx: Updated SearchTitlesFilter handler - Application.tsx: Added createSwitchRegister helper - Details.tsx: Updated public branding handler - Features.tsx: Created individual handlers for seamless edit and commenting - OAuthClientForm.tsx: Added createSwitchRegister helper - Maintained backward compatibility with react-hook-form - Improved type safety and code clarity * Fix ESLint floating promise errors - Add void operator to onChange call in CollectionForm.tsx - Add void operator to document.save call in DocumentMenu.tsx These changes fix the @typescript-eslint/no-floating-promises errors while maintaining the existing functionality. * Fix Switch component onChange handlers in remaining files - Updated DocumentCopy.tsx handlers to use boolean parameter - Updated Notifications.tsx to use closure pattern for event types - Updated SlackListItem.tsx to use closure pattern for event names - All TypeScript errors resolved * Refactor createSwitchRegister into utils/forms - Created shared utility function in app/utils/forms.ts - Removed duplicate implementations from CollectionForm, Application, and OAuthClientForm - Updated all usage points to use the shared utility - Improved TypeScript typing with Record<string, unknown> - Fixed imports and removed unused variables - Maintained existing functionality while reducing code duplication * Fix TypeScript errors in createSwitchRegister utility - Updated generic constraint from Record<string, unknown> to FieldValues - Added Path import from react-hook-form for proper type safety - Fixed parameter type from keyof TFormData to Path<TFormData> - Improved type compatibility with react-hook-form's UseFormRegister Resolves TypeScript compilation errors in CI pipeline. * Remove unnecessary handlePublishChange callbacks - Removed handlePublishChange wrapper in DocumentCopy.tsx - Removed handlePublishChange wrapper in TemplatizeDialog/index.tsx - Updated Switch components to use setPublish directly - Simplified code by leveraging boolean callback from Switch component Since Switch now passes boolean directly, no need for intermediate callbacks. * Address review feedback: simplify callbacks and fix fullWidth behavior 1. DocumentCopy.tsx: - Remove handleRecursiveChange callback wrapper - Use setRecursive directly with Switch component 2. DocumentMenu.tsx: - Add void user.save() to persist user preference - Add document.fullWidth = checked for optimistic update behavior Both changes leverage the boolean callback from Switch component properly. * Update Security.tsx * i18n --------- Co-authored-by: codegen-sh[bot] <131295404+codegen-sh[bot]@users.noreply.github.com> Co-authored-by: Tom Moor <tom@getoutline.com>
This commit is contained in:
@@ -22,6 +22,7 @@ import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { EmptySelectValue } from "~/types";
|
||||
import { createSwitchRegister } from "~/utils/forms";
|
||||
|
||||
const IconPicker = createLazyComponent(() => import("~/components/IconPicker"));
|
||||
|
||||
@@ -114,7 +115,7 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
}, [setFocus]);
|
||||
|
||||
const handleIconChange = useCallback(
|
||||
(icon: string, color: string | null) => {
|
||||
(icon: string, color: string) => {
|
||||
if (icon !== values.icon) {
|
||||
setFocus("name");
|
||||
}
|
||||
@@ -131,7 +132,6 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
<Trans>
|
||||
Collections are used to group documents and choose permissions
|
||||
</Trans>
|
||||
.
|
||||
</Text>
|
||||
<Flex gap={8}>
|
||||
<Input
|
||||
@@ -188,7 +188,7 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
note={t(
|
||||
"Allow documents within this collection to be shared publicly on the internet."
|
||||
)}
|
||||
{...register("sharing")}
|
||||
{...createSwitchRegister(register, "sharing")}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -197,7 +197,7 @@ export const CollectionForm = observer(function CollectionForm_({
|
||||
id="commenting"
|
||||
label={t("Commenting")}
|
||||
note={t("Allow commenting on documents within this collection.")}
|
||||
{...register("commenting")}
|
||||
{...createSwitchRegister(register, "commenting")}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -46,20 +46,6 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
return nodes;
|
||||
}, [policies, collectionTrees, document.isTemplate]);
|
||||
|
||||
const handlePublishChange = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPublish(ev.target.checked);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleRecursiveChange = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRecursive(ev.target.checked);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const copy = async () => {
|
||||
if (!selectedPath) {
|
||||
toast.message(t("Select a location to copy"));
|
||||
@@ -102,7 +88,7 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
label={t("Publish")}
|
||||
labelPosition="right"
|
||||
checked={publish}
|
||||
onChange={handlePublishChange}
|
||||
onChange={setPublish}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
@@ -113,7 +99,7 @@ function DocumentCopy({ document, onSubmit }: Props) {
|
||||
label={t("Include nested documents")}
|
||||
labelPosition="right"
|
||||
checked={recursive}
|
||||
onChange={handleRecursiveChange}
|
||||
onChange={setRecursive}
|
||||
/>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import ImageInput from "~/scenes/Settings/components/ImageInput";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input, { LabelText } from "~/components/Input";
|
||||
import { createSwitchRegister } from "~/utils/forms";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import Switch from "../Switch";
|
||||
|
||||
@@ -116,7 +117,7 @@ export const OAuthClientForm = observer(function OAuthClientForm_({
|
||||
/>
|
||||
{isCloudHosted && (
|
||||
<Switch
|
||||
{...register("published")}
|
||||
{...createSwitchRegister(register, "published")}
|
||||
label={t("Published")}
|
||||
note={t("Allow this app to be installed by other workspaces")}
|
||||
/>
|
||||
|
||||
@@ -52,10 +52,10 @@ function PublicAccess({ document, share, sharedParent }: Props) {
|
||||
}, [share?.urlId]);
|
||||
|
||||
const handleIndexingChanged = React.useCallback(
|
||||
async (event) => {
|
||||
async (checked: boolean) => {
|
||||
try {
|
||||
await share?.save({
|
||||
allowIndexing: event.currentTarget.checked,
|
||||
allowIndexing: checked,
|
||||
});
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
@@ -65,10 +65,10 @@ function PublicAccess({ document, share, sharedParent }: Props) {
|
||||
);
|
||||
|
||||
const handleShowLastModifiedChanged = React.useCallback(
|
||||
async (event) => {
|
||||
async (checked: boolean) => {
|
||||
try {
|
||||
await share?.save({
|
||||
showLastUpdated: event.currentTarget.checked,
|
||||
showLastUpdated: checked,
|
||||
});
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
@@ -78,10 +78,10 @@ function PublicAccess({ document, share, sharedParent }: Props) {
|
||||
);
|
||||
|
||||
const handlePublishedChange = React.useCallback(
|
||||
async (event) => {
|
||||
async (checked: boolean) => {
|
||||
try {
|
||||
await share?.save({
|
||||
published: event.currentTarget.checked,
|
||||
published: checked,
|
||||
});
|
||||
} catch (err) {
|
||||
toast.error(err.message);
|
||||
|
||||
@@ -28,7 +28,7 @@ interface Props
|
||||
/** Whether the switch is disabled */
|
||||
disabled?: boolean;
|
||||
/** Callback when the switch state changes */
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onChange?: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
function Switch(
|
||||
@@ -49,15 +49,10 @@ function Switch(
|
||||
const handleCheckedChange = React.useCallback(
|
||||
(checkedState: boolean) => {
|
||||
if (onChange) {
|
||||
// Create a synthetic event to maintain compatibility with existing code
|
||||
const syntheticEvent = {
|
||||
target: { id: props.id, name: props.name, checked: checkedState },
|
||||
currentTarget: { checked: checkedState },
|
||||
} as React.ChangeEvent<HTMLInputElement>;
|
||||
onChange(syntheticEvent);
|
||||
onChange(checkedState);
|
||||
}
|
||||
},
|
||||
[onChange, props.id, props.name]
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const component = (
|
||||
|
||||
@@ -27,13 +27,6 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
|
||||
document.collectionId ?? null
|
||||
);
|
||||
|
||||
const handlePublishChange = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPublish(ev.target.checked);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleSubmit = React.useCallback(async () => {
|
||||
const template = await document?.templatize({
|
||||
collectionId,
|
||||
@@ -72,7 +65,7 @@ function DocumentTemplatizeDialog({ documentId }: Props) {
|
||||
label={t("Published")}
|
||||
note={t("Enable other members to use the template immediately")}
|
||||
checked={publish}
|
||||
onChange={handlePublishChange}
|
||||
onChange={setPublish}
|
||||
/>
|
||||
</Flex>
|
||||
</ConfirmationDialog>
|
||||
|
||||
@@ -234,6 +234,27 @@ const MenuContent: React.FC<MenuContentProps> = observer(function MenuContent_({
|
||||
onSelectTemplate,
|
||||
});
|
||||
|
||||
const handleEmbedsToggle = React.useCallback(
|
||||
(checked: boolean) => {
|
||||
if (checked) {
|
||||
document.enableEmbeds();
|
||||
} else {
|
||||
document.disableEmbeds();
|
||||
}
|
||||
},
|
||||
[document]
|
||||
);
|
||||
|
||||
const handleFullWidthToggle = React.useCallback(
|
||||
(checked: boolean) => {
|
||||
user.setPreference(UserPreference.FullWidthDocuments, checked);
|
||||
void user.save();
|
||||
document.fullWidth = checked;
|
||||
void document.save({ fullWidth: checked });
|
||||
},
|
||||
[user, document]
|
||||
);
|
||||
|
||||
return !isEmpty(can) ? (
|
||||
<ContextMenu
|
||||
{...menuState}
|
||||
@@ -364,11 +385,7 @@ const MenuContent: React.FC<MenuContentProps> = observer(function MenuContent_({
|
||||
label={t("Enable embeds")}
|
||||
labelPosition="left"
|
||||
checked={!document.embedsDisabled}
|
||||
onChange={
|
||||
document.embedsDisabled
|
||||
? document.enableEmbeds
|
||||
: document.disableEmbeds
|
||||
}
|
||||
onChange={handleEmbedsToggle}
|
||||
/>
|
||||
</Style>
|
||||
)}
|
||||
@@ -380,16 +397,7 @@ const MenuContent: React.FC<MenuContentProps> = observer(function MenuContent_({
|
||||
label={t("Full width")}
|
||||
labelPosition="left"
|
||||
checked={document.fullWidth}
|
||||
onChange={(ev) => {
|
||||
const fullWidth = ev.currentTarget.checked;
|
||||
user.setPreference(
|
||||
UserPreference.FullWidthDocuments,
|
||||
fullWidth
|
||||
);
|
||||
void user.save();
|
||||
document.fullWidth = fullWidth;
|
||||
void document.save();
|
||||
}}
|
||||
onChange={handleFullWidthToggle}
|
||||
/>
|
||||
</Style>
|
||||
)}
|
||||
|
||||
@@ -271,8 +271,8 @@ function Search() {
|
||||
width={26}
|
||||
height={14}
|
||||
label={t("Search titles only")}
|
||||
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
handleFilterChange({ titleFilter: ev.target.checked });
|
||||
onChange={(checked: boolean) => {
|
||||
handleFilterChange({ titleFilter: checked });
|
||||
}}
|
||||
checked={titleFilter}
|
||||
/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { OAuthClientValidation } from "@shared/validations";
|
||||
import OAuthClient from "~/models/oauth/OAuthClient";
|
||||
import Breadcrumb from "~/components/Breadcrumb";
|
||||
@@ -22,6 +23,7 @@ import Tooltip from "~/components/Tooltip";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import OAuthClientMenu from "~/menus/OAuthClientMenu";
|
||||
import { createSwitchRegister } from "~/utils/forms";
|
||||
import isCloudHosted from "~/utils/isCloudHosted";
|
||||
import { settingsPath } from "~/utils/routeHelpers";
|
||||
import { ActionRow } from "./components/ActionRow";
|
||||
@@ -218,7 +220,10 @@ const Application = observer(function Application({ oauthClient }: Props) {
|
||||
)}
|
||||
border={false}
|
||||
>
|
||||
<Switch id="published" {...register("published")} />
|
||||
<Switch
|
||||
id="published"
|
||||
{...createSwitchRegister(register, "published")}
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
|
||||
|
||||
@@ -274,9 +274,7 @@ function Details() {
|
||||
id={TeamPreference.PublicBranding}
|
||||
name={TeamPreference.PublicBranding}
|
||||
checked={publicBranding}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setPublicBranding(event.target.checked)
|
||||
}
|
||||
onChange={(checked: boolean) => setPublicBranding(checked)}
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
|
||||
@@ -15,16 +15,23 @@ function Features() {
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handlePreferenceChange =
|
||||
(inverted = false) =>
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
team.setPreference(
|
||||
ev.target.name as TeamPreference,
|
||||
inverted ? !ev.target.checked : ev.target.checked
|
||||
);
|
||||
const handleSeamlessEditChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
team.setPreference(TeamPreference.SeamlessEdit, !checked);
|
||||
await team.save();
|
||||
toast.success(t("Settings saved"));
|
||||
};
|
||||
},
|
||||
[team, t]
|
||||
);
|
||||
|
||||
const handleCommentingChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
team.setPreference(TeamPreference.Commenting, checked);
|
||||
await team.save();
|
||||
toast.success(t("Settings saved"));
|
||||
},
|
||||
[team, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<Scene title={t("Features")} icon={<BeakerIcon />}>
|
||||
@@ -46,7 +53,7 @@ function Features() {
|
||||
id={TeamPreference.SeamlessEdit}
|
||||
name={TeamPreference.SeamlessEdit}
|
||||
checked={!team.getPreference(TeamPreference.SeamlessEdit)}
|
||||
onChange={handlePreferenceChange(true)}
|
||||
onChange={handleSeamlessEditChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -60,7 +67,7 @@ function Features() {
|
||||
id={TeamPreference.Commenting}
|
||||
name={TeamPreference.Commenting}
|
||||
checked={team.getPreference(TeamPreference.Commenting)}
|
||||
onChange={handlePreferenceChange(false)}
|
||||
onChange={handleCommentingChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
</Scene>
|
||||
|
||||
@@ -138,15 +138,13 @@ function Notifications() {
|
||||
}, 500);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
await user.setNotificationEventType(
|
||||
ev.target.name as NotificationEventType,
|
||||
ev.target.checked
|
||||
);
|
||||
(eventType: NotificationEventType) => async (checked: boolean) => {
|
||||
await user.setNotificationEventType(eventType, checked);
|
||||
showSuccessMessage();
|
||||
},
|
||||
[user, showSuccessMessage]
|
||||
);
|
||||
|
||||
const showSuccessNotice = window.location.search === "?success";
|
||||
|
||||
return (
|
||||
@@ -196,7 +194,7 @@ function Notifications() {
|
||||
id={option.event}
|
||||
name={option.event}
|
||||
checked={!!setting}
|
||||
onChange={handleChange}
|
||||
onChange={handleChange(option.event)}
|
||||
/>
|
||||
</SettingRow>
|
||||
);
|
||||
|
||||
@@ -49,16 +49,50 @@ function Preferences() {
|
||||
[t]
|
||||
);
|
||||
|
||||
const handlePreferenceChange =
|
||||
(inverted = false) =>
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
user.setPreference(
|
||||
ev.target.name as UserPreference,
|
||||
inverted ? !ev.target.checked : ev.target.checked
|
||||
);
|
||||
const handleUseCursorPointerChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
user.setPreference(UserPreference.UseCursorPointer, checked);
|
||||
await user.save();
|
||||
toast.success(t("Preferences saved"));
|
||||
};
|
||||
},
|
||||
[user, t]
|
||||
);
|
||||
|
||||
const handleCodeBlockLineNumbersChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
user.setPreference(UserPreference.CodeBlockLineNumers, checked);
|
||||
await user.save();
|
||||
toast.success(t("Preferences saved"));
|
||||
},
|
||||
[user, t]
|
||||
);
|
||||
|
||||
const handleSeamlessEditChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
user.setPreference(UserPreference.SeamlessEdit, !checked);
|
||||
await user.save();
|
||||
toast.success(t("Preferences saved"));
|
||||
},
|
||||
[user, t]
|
||||
);
|
||||
|
||||
const handleRememberLastPathChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
user.setPreference(UserPreference.RememberLastPath, checked);
|
||||
await user.save();
|
||||
toast.success(t("Preferences saved"));
|
||||
},
|
||||
[user, t]
|
||||
);
|
||||
|
||||
const handleEnableSmartTextChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
user.setPreference(UserPreference.EnableSmartText, checked);
|
||||
await user.save();
|
||||
toast.success(t("Preferences saved"));
|
||||
},
|
||||
[user, t]
|
||||
);
|
||||
|
||||
const handleLanguageChange = React.useCallback(
|
||||
async (language: string) => {
|
||||
@@ -145,7 +179,7 @@ function Preferences() {
|
||||
id={UserPreference.UseCursorPointer}
|
||||
name={UserPreference.UseCursorPointer}
|
||||
checked={user.getPreference(UserPreference.UseCursorPointer)}
|
||||
onChange={handlePreferenceChange(false)}
|
||||
onChange={handleUseCursorPointerChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -158,7 +192,7 @@ function Preferences() {
|
||||
id={UserPreference.CodeBlockLineNumers}
|
||||
name={UserPreference.CodeBlockLineNumers}
|
||||
checked={user.getPreference(UserPreference.CodeBlockLineNumers)}
|
||||
onChange={handlePreferenceChange(false)}
|
||||
onChange={handleCodeBlockLineNumbersChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
@@ -179,7 +213,7 @@ function Preferences() {
|
||||
team.getPreference(TeamPreference.SeamlessEdit)
|
||||
)
|
||||
}
|
||||
onChange={handlePreferenceChange(true)}
|
||||
onChange={handleSeamlessEditChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -193,7 +227,7 @@ function Preferences() {
|
||||
id={UserPreference.RememberLastPath}
|
||||
name={UserPreference.RememberLastPath}
|
||||
checked={!!user.getPreference(UserPreference.RememberLastPath)}
|
||||
onChange={handlePreferenceChange(false)}
|
||||
onChange={handleRememberLastPathChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -208,7 +242,7 @@ function Preferences() {
|
||||
id={UserPreference.EnableSmartText}
|
||||
name={UserPreference.EnableSmartText}
|
||||
checked={!!user.getPreference(UserPreference.EnableSmartText)}
|
||||
onChange={handlePreferenceChange(false)}
|
||||
onChange={handleEnableSmartTextChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
|
||||
@@ -88,13 +88,6 @@ function Security() {
|
||||
[team, showSuccessMessage]
|
||||
);
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
await saveData({ [ev.target.id]: ev.target.checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleDefaultRoleChange = React.useCallback(
|
||||
async (newDefaultRole: string) => {
|
||||
await saveData({ defaultUserRole: newDefaultRole });
|
||||
@@ -102,11 +95,68 @@ function Security() {
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handlePreferenceChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleGuestSigninChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
await saveData({ guestSignin: checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleSharingChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
await saveData({ sharing: checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleDocumentEmbedsChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
await saveData({ documentEmbeds: checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleMemberCollectionCreateChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
await saveData({ memberCollectionCreate: checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleMemberTeamCreateChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
await saveData({ memberTeamCreate: checked });
|
||||
},
|
||||
[saveData]
|
||||
);
|
||||
|
||||
const handleMembersCanInviteChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
const preferences = {
|
||||
...team.preferences,
|
||||
[ev.target.id]: ev.target.checked,
|
||||
[TeamPreference.MembersCanInvite]: checked,
|
||||
};
|
||||
await saveData({ preferences });
|
||||
},
|
||||
[saveData, team.preferences]
|
||||
);
|
||||
|
||||
const handleViewersCanExportChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
const preferences = {
|
||||
...team.preferences,
|
||||
[TeamPreference.ViewersCanExport]: checked,
|
||||
};
|
||||
await saveData({ preferences });
|
||||
},
|
||||
[saveData, team.preferences]
|
||||
);
|
||||
|
||||
const handleMembersCanDeleteAccountChange = React.useCallback(
|
||||
async (checked: boolean) => {
|
||||
const preferences = {
|
||||
...team.preferences,
|
||||
[TeamPreference.MembersCanDeleteAccount]: checked,
|
||||
};
|
||||
await saveData({ preferences });
|
||||
},
|
||||
@@ -114,8 +164,8 @@ function Security() {
|
||||
);
|
||||
|
||||
const handleInviteRequiredChange = React.useCallback(
|
||||
async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const inviteRequired = ev.target.checked;
|
||||
async (checked: boolean) => {
|
||||
const inviteRequired = checked;
|
||||
const newData = { ...data, inviteRequired };
|
||||
|
||||
if (inviteRequired) {
|
||||
@@ -141,10 +191,9 @@ function Security() {
|
||||
</ConfirmationDialog>
|
||||
),
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
await saveData(newData);
|
||||
}
|
||||
|
||||
await saveData(newData);
|
||||
},
|
||||
[data, saveData, t, dialogs, team.signinMethods]
|
||||
);
|
||||
@@ -204,7 +253,7 @@ function Security() {
|
||||
<Switch
|
||||
id="guestSignin"
|
||||
checked={data.guestSignin}
|
||||
onChange={handleChange}
|
||||
onChange={handleGuestSigninChange}
|
||||
disabled={!env.EMAIL_ENABLED}
|
||||
/>
|
||||
</SettingRow>
|
||||
@@ -218,7 +267,7 @@ function Security() {
|
||||
<Switch
|
||||
id={TeamPreference.MembersCanInvite}
|
||||
checked={team.getPreference(TeamPreference.MembersCanInvite)}
|
||||
onChange={handlePreferenceChange}
|
||||
onChange={handleMembersCanInviteChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
{isCloudHosted && (
|
||||
@@ -268,7 +317,11 @@ function Security() {
|
||||
"When enabled, documents can be shared publicly on the internet by any member of the workspace"
|
||||
)}
|
||||
>
|
||||
<Switch id="sharing" checked={data.sharing} onChange={handleChange} />
|
||||
<Switch
|
||||
id="sharing"
|
||||
checked={data.sharing}
|
||||
onChange={handleSharingChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("Viewer document exports")}
|
||||
@@ -280,7 +333,7 @@ function Security() {
|
||||
<Switch
|
||||
id={TeamPreference.ViewersCanExport}
|
||||
checked={team.getPreference(TeamPreference.ViewersCanExport)}
|
||||
onChange={handlePreferenceChange}
|
||||
onChange={handleViewersCanExportChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -293,7 +346,7 @@ function Security() {
|
||||
<Switch
|
||||
id={TeamPreference.MembersCanDeleteAccount}
|
||||
checked={team.getPreference(TeamPreference.MembersCanDeleteAccount)}
|
||||
onChange={handlePreferenceChange}
|
||||
onChange={handleMembersCanDeleteAccountChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -306,7 +359,7 @@ function Security() {
|
||||
<Switch
|
||||
id="documentEmbeds"
|
||||
checked={data.documentEmbeds}
|
||||
onChange={handleChange}
|
||||
onChange={handleDocumentEmbedsChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
@@ -319,7 +372,7 @@ function Security() {
|
||||
<Switch
|
||||
id="memberCollectionCreate"
|
||||
checked={data.memberCollectionCreate}
|
||||
onChange={handleChange}
|
||||
onChange={handleMemberCollectionCreateChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
{isCloudHosted && (
|
||||
@@ -331,7 +384,7 @@ function Security() {
|
||||
<Switch
|
||||
id="memberTeamCreate"
|
||||
checked={data.memberTeamCreate}
|
||||
onChange={handleChange}
|
||||
onChange={handleMemberTeamCreateChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
)}
|
||||
|
||||
25
app/utils/forms.ts
Normal file
25
app/utils/forms.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { UseFormRegister, Path, FieldValues } from "react-hook-form";
|
||||
|
||||
/**
|
||||
* Creates a switch register function that adapts react-hook-form's register
|
||||
* to work with Switch components that use boolean callbacks instead of synthetic events.
|
||||
*
|
||||
* @param register - The register function from react-hook-form
|
||||
* @param fieldName - The name of the form field to register
|
||||
* @returns An object with the registration props adapted for Switch components
|
||||
*/
|
||||
export function createSwitchRegister<
|
||||
TFormData extends FieldValues = FieldValues
|
||||
>(register: UseFormRegister<TFormData>, fieldName: Path<TFormData>) {
|
||||
const { onChange, ...rest } = register(fieldName);
|
||||
return {
|
||||
...rest,
|
||||
onChange: (checked: boolean) => {
|
||||
const syntheticEvent = {
|
||||
target: { name: fieldName, value: checked, checked },
|
||||
type: "change",
|
||||
};
|
||||
void onChange(syntheticEvent);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -26,13 +26,11 @@ type Props = {
|
||||
function SlackListItem({ integration, collection }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = async (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (ev.target.checked) {
|
||||
integration.events = uniq([...integration.events, ev.target.name]);
|
||||
const handleChange = (eventName: string) => async (checked: boolean) => {
|
||||
if (checked) {
|
||||
integration.events = uniq([...integration.events, eventName]);
|
||||
} else {
|
||||
integration.events = integration.events.filter(
|
||||
(n) => n !== ev.target.name
|
||||
);
|
||||
integration.events = integration.events.filter((n) => n !== eventName);
|
||||
}
|
||||
|
||||
await integration.save();
|
||||
@@ -87,13 +85,13 @@ function SlackListItem({ integration, collection }: Props) {
|
||||
label={t("Document published")}
|
||||
name="documents.publish"
|
||||
checked={integration.events.includes("documents.publish")}
|
||||
onChange={handleChange}
|
||||
onChange={handleChange("documents.publish")}
|
||||
/>
|
||||
<Switch
|
||||
label={t("Document updated")}
|
||||
name="documents.update"
|
||||
checked={integration.events.includes("documents.update")}
|
||||
onChange={handleChange}
|
||||
onChange={handleChange("documents.update")}
|
||||
/>
|
||||
</Events>
|
||||
</Popover>
|
||||
|
||||
Reference in New Issue
Block a user