Files
actual/packages/desktop-client/src/components/settings/Experimental.tsx
2025-11-01 14:18:08 +00:00

202 lines
5.7 KiB
TypeScript

import { type ReactNode, useState } from 'react';
import { Trans } from 'react-i18next';
import { Text } from '@actual-app/components/text';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import type { FeatureFlag, SyncedPrefs } from 'loot-core/types/prefs';
import { Setting } from './UI';
import { Link } from '@desktop-client/components/common/Link';
import { Checkbox } from '@desktop-client/components/forms';
import { useFeatureFlag } from '@desktop-client/hooks/useFeatureFlag';
import { useSyncedPref } from '@desktop-client/hooks/useSyncedPref';
type FeatureToggleProps = {
flag: FeatureFlag;
disableToggle?: boolean;
error?: ReactNode;
children: ReactNode;
feedbackLink?: string;
};
function FeatureToggle({
flag: flagName,
disableToggle = false,
feedbackLink,
error,
children,
}: FeatureToggleProps) {
const enabled = useFeatureFlag(flagName);
const [_, setFlagPref] = useSyncedPref(`flags.${flagName}`);
return (
<label style={{ display: 'flex' }}>
<Checkbox
checked={enabled}
onChange={() => {
setFlagPref(String(!enabled));
}}
disabled={disableToggle}
/>
<View
style={{ color: disableToggle ? theme.pageTextSubdued : 'inherit' }}
>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
{children}
{feedbackLink && (
<Link variant="external" to={feedbackLink}>
<Trans>(give feedback)</Trans>
</Link>
)}
</View>
{disableToggle && (
<Text
style={{
color: theme.errorText,
fontWeight: 500,
}}
>
{error}
</Text>
)}
</View>
</label>
);
}
type GlobalFeatureToggleProps = {
prefName: keyof SyncedPrefs;
disableToggle?: boolean;
error?: ReactNode;
children: ReactNode;
feedbackLink?: string;
};
function GlobalFeatureToggle({
prefName,
disableToggle = false,
feedbackLink,
error,
children,
}: GlobalFeatureToggleProps) {
const [enabled, setEnabled] = useSyncedPref(prefName);
return (
<label style={{ display: 'flex' }}>
<Checkbox
checked={enabled === 'true'}
onChange={() => {
setEnabled(enabled === 'true' ? 'false' : 'true');
}}
disabled={disableToggle}
/>
<View
style={{ color: disableToggle ? theme.pageTextSubdued : 'inherit' }}
>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
{children}
{feedbackLink && (
<Link variant="external" to={feedbackLink}>
<Trans>(give feedback)</Trans>
</Link>
)}
</View>
{disableToggle && (
<Text
style={{
color: theme.errorText,
fontWeight: 500,
}}
>
{error}
</Text>
)}
</View>
</label>
);
}
export function ExperimentalFeatures() {
const [expanded, setExpanded] = useState(false);
const goalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
const goalTemplatesUIEnabled = useFeatureFlag('goalTemplatesUIEnabled');
const showGoalTemplatesUI =
goalTemplatesUIEnabled ||
(goalTemplatesEnabled &&
localStorage.getItem('devEnableGoalTemplatesUI') === 'true');
return (
<Setting
primaryAction={
expanded ? (
<View style={{ gap: '1em' }}>
<FeatureToggle flag="goalTemplatesEnabled">
<Trans>Goal templates</Trans>
</FeatureToggle>
{showGoalTemplatesUI && (
<View style={{ paddingLeft: 22 }}>
<FeatureToggle flag="goalTemplatesUIEnabled">
<Trans>Subfeature: Budget automations UI</Trans>
</FeatureToggle>
</View>
)}
<FeatureToggle
flag="actionTemplating"
feedbackLink="https://github.com/actualbudget/actual/issues/3606"
>
<Trans>Rule action templating</Trans>
</FeatureToggle>
<FeatureToggle
flag="formulaMode"
feedbackLink="https://github.com/actualbudget/actual/issues/5949"
>
<Trans>Excel formula mode (Formula cards & Rule formulas)</Trans>
</FeatureToggle>
<FeatureToggle
flag="currency"
feedbackLink="https://github.com/actualbudget/actual/issues/5191"
>
<Trans>Currency support</Trans>
</FeatureToggle>
<GlobalFeatureToggle
prefName="plugins"
disableToggle={true}
feedbackLink="https://github.com/actualbudget/actual/issues/5950"
>
<Trans>Client-Side plugins (soon)</Trans>
</GlobalFeatureToggle>
</View>
) : (
<Link
variant="text"
onClick={() => setExpanded(true)}
data-testid="experimental-settings"
style={{
flexShrink: 0,
alignSelf: 'flex-start',
color: theme.pageTextPositive,
}}
>
<Trans>I understand the risks, show experimental features</Trans>
</Link>
)
}
>
<Text>
<Trans>
<strong>Experimental features.</strong> These features are not fully
tested and may not work as expected. THEY MAY CAUSE IRRECOVERABLE DATA
LOSS. They may do nothing at all. Only enable them if you know what
you are doing.
</Trans>
</Text>
</Setting>
);
}