From e22d9ade6d67e31cf3ffeac7b77489b9d4388cbc Mon Sep 17 00:00:00 2001
From: Jed Fox <git@jedfox.com>
Date: Wed, 18 Jan 2023 17:38:46 -0500
Subject: [PATCH] =?UTF-8?q?Add=20an=20=E2=80=9CExperimental=20Features?=
 =?UTF-8?q?=E2=80=9D=20section=20in=20the=20settings=20(#467)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Make ButtonSetting more generic

* Add an “Experimental Features” section in the settings

* Fix text color

* Put experimental features behind another link

* Update Encryption.js

* Update FixSplits.js

* Remove unused variable

* That’s what I get for coding on my phone

Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
---
 .../src/components/settings/Encryption.js     | 16 ++--
 .../src/components/settings/Experimental.js   | 79 +++++++++++++++++++
 .../src/components/settings/Export.js         |  6 +-
 .../src/components/settings/FixSplits.js      |  6 +-
 .../src/components/settings/Reset.js          | 14 ++--
 .../src/components/settings/UI.js             | 11 ++-
 .../src/components/settings/index.js          |  2 +
 7 files changed, 110 insertions(+), 24 deletions(-)
 create mode 100644 packages/desktop-client/src/components/settings/Experimental.js

diff --git a/packages/desktop-client/src/components/settings/Encryption.js b/packages/desktop-client/src/components/settings/Encryption.js
index 58f9ffc2d..5830ad72e 100644
--- a/packages/desktop-client/src/components/settings/Encryption.js
+++ b/packages/desktop-client/src/components/settings/Encryption.js
@@ -4,7 +4,7 @@ import { Text, Button } from 'loot-design/src/components/common';
 import { colors } from 'loot-design/src/style';
 
 import { useServerURL } from '../../hooks/useServerURL';
-import { ButtonSetting } from './UI';
+import { Setting } from './UI';
 
 export default function EncryptionSettings({ prefs, pushModal }) {
   const serverURL = useServerURL();
@@ -14,8 +14,8 @@ export default function EncryptionSettings({ prefs, pushModal }) {
   }
 
   return prefs.encryptKeyId ? (
-    <ButtonSetting
-      button={<Button onClick={onChangeKey}>Generate new key</Button>}
+    <Setting
+      primaryAction={<Button onClick={onChangeKey}>Generate new key</Button>}
     >
       <Text>
         <Text style={{ color: colors.g4, fontWeight: 600 }}>
@@ -32,9 +32,9 @@ export default function EncryptionSettings({ prefs, pushModal }) {
           Learn more…
         </a>
       </Text>
-    </ButtonSetting>
+    </Setting>
   ) : serverURL ? (
-    <ButtonSetting
+    <Setting
       button={
         <Button onClick={() => pushModal('create-encryption-key')}>
           Enable encryption…
@@ -55,9 +55,9 @@ export default function EncryptionSettings({ prefs, pushModal }) {
           Learn more…
         </a>
       </Text>
-    </ButtonSetting>
+    </Setting>
   ) : (
-    <ButtonSetting button={<Button disabled>Enable encryption…</Button>}>
+    <Setting button={<Button disabled>Enable encryption…</Button>}>
       <Text>
         <strong>End-to-end encryption</strong> is not available when running
         without a server. Budget files are always kept unencrypted locally, and
@@ -70,6 +70,6 @@ export default function EncryptionSettings({ prefs, pushModal }) {
           Learn more…
         </a>
       </Text>
-    </ButtonSetting>
+    </Setting>
   );
 }
diff --git a/packages/desktop-client/src/components/settings/Experimental.js b/packages/desktop-client/src/components/settings/Experimental.js
new file mode 100644
index 000000000..f3e96b5ad
--- /dev/null
+++ b/packages/desktop-client/src/components/settings/Experimental.js
@@ -0,0 +1,79 @@
+import React from 'react';
+
+import { Link, Text, View } from 'loot-design/src/components/common';
+import { Checkbox } from 'loot-design/src/components/forms';
+import { colors } from 'loot-design/src/style';
+
+import { Setting } from './UI';
+
+export default function ExperimentalFeatures({ prefs, savePrefs }) {
+  let [expanded, setExpanded] = React.useState(false);
+  let flags = Object.fromEntries(
+    Object.entries(prefs)
+      .filter(([key]) => key.startsWith('flags.'))
+      .map(([key, value]) => [key.replace('flags.', ''), value])
+  );
+  let disabled = prefs.budgetType === 'report' && flags.reportBudget;
+  return (
+    <Setting
+      primaryAction={
+        expanded ? (
+          <View style={{ gap: '1em' }}>
+            <label
+              style={{
+                display: 'flex',
+                color: disabled ? colors.n5 : 'inherit'
+              }}
+            >
+              <Checkbox
+                id="report-budget-flag"
+                checked={flags.reportBudget}
+                onChange={() => {
+                  savePrefs({ 'flags.reportBudget': !flags.reportBudget });
+                }}
+                disabled={disabled}
+              />{' '}
+              <View>
+                Enable budget mode toggle
+                {disabled && (
+                  <Text style={{ color: colors.r3, fontWeight: 500 }}>
+                    Switch to a rollover budget before turning off this feature
+                  </Text>
+                )}
+              </View>
+            </label>
+
+            <label style={{ display: 'flex' }}>
+              <Checkbox
+                id="report-budget-flag"
+                checked={flags.syncAccount}
+                onChange={() => {
+                  savePrefs({ 'flags.syncAccount': !flags.syncAccount });
+                }}
+              />{' '}
+              <View>Enable account syncing</View>
+            </label>
+          </View>
+        ) : (
+          <Link
+            onClick={() => setExpanded(true)}
+            style={{
+              flexShrink: 0,
+              alignSelf: 'flex-start',
+              color: colors.p4
+            }}
+          >
+            I understand the risks, show experimental features
+          </Link>
+        )
+      }
+    >
+      <Text>
+        <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.
+      </Text>
+    </Setting>
+  );
+}
diff --git a/packages/desktop-client/src/components/settings/Export.js b/packages/desktop-client/src/components/settings/Export.js
index 9a21cca1c..6f28e95fd 100644
--- a/packages/desktop-client/src/components/settings/Export.js
+++ b/packages/desktop-client/src/components/settings/Export.js
@@ -3,7 +3,7 @@ import React from 'react';
 import { send } from 'loot-core/src/platform/client/fetch';
 import { Text, Button } from 'loot-design/src/components/common';
 
-import { ButtonSetting } from './UI';
+import { Setting } from './UI';
 
 export default function ExportBudget({ prefs }) {
   async function onExport() {
@@ -12,7 +12,7 @@ export default function ExportBudget({ prefs }) {
   }
 
   return (
-    <ButtonSetting button={<Button onClick={onExport}>Export data</Button>}>
+    <Setting primaryAction={<Button onClick={onExport}>Export data</Button>}>
       <Text>
         <strong>Export</strong> your data as a zip file containing{' '}
         <code>db.sqlite</code> and <code>metadata.json</code> files. It can be
@@ -25,6 +25,6 @@ export default function ExportBudget({ prefs }) {
           any encryption.
         </Text>
       ) : null}
-    </ButtonSetting>
+    </Setting>
   );
 }
diff --git a/packages/desktop-client/src/components/settings/FixSplits.js b/packages/desktop-client/src/components/settings/FixSplits.js
index 7a9b4ae02..1aa27cc04 100644
--- a/packages/desktop-client/src/components/settings/FixSplits.js
+++ b/packages/desktop-client/src/components/settings/FixSplits.js
@@ -9,7 +9,7 @@ import {
 } from 'loot-design/src/components/common';
 import { colors } from 'loot-design/src/style';
 
-import { ButtonSetting } from './UI';
+import { Setting } from './UI';
 
 function renderResults(results) {
   let { numBlankPayees, numCleared, numDeleted } = results;
@@ -61,7 +61,7 @@ export default function FixSplitsTool() {
   }
 
   return (
-    <ButtonSetting
+    <Setting
       button={
         <View
           style={{
@@ -104,6 +104,6 @@ export default function FixSplitsTool() {
           </ul>
         </P>
       </View>
-    </ButtonSetting>
+    </Setting>
   );
 }
diff --git a/packages/desktop-client/src/components/settings/Reset.js b/packages/desktop-client/src/components/settings/Reset.js
index 119e9197c..91dbefd39 100644
--- a/packages/desktop-client/src/components/settings/Reset.js
+++ b/packages/desktop-client/src/components/settings/Reset.js
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
 import { send } from 'loot-core/src/platform/client/fetch';
 import { Text, ButtonWithLoading } from 'loot-design/src/components/common';
 
-import { ButtonSetting } from './UI';
+import { Setting } from './UI';
 
 export function ResetCache() {
   let [resetting, setResetting] = useState(false);
@@ -15,8 +15,8 @@ export function ResetCache() {
   }
 
   return (
-    <ButtonSetting
-      button={
+    <Setting
+      primaryAction={
         <ButtonWithLoading loading={resetting} onClick={onResetCache}>
           Reset budget cache
         </ButtonWithLoading>
@@ -29,7 +29,7 @@ export function ResetCache() {
         won't see correct values. There is no danger in resetting the cache.
         Hopefully you never have to do this.
       </Text>
-    </ButtonSetting>
+    </Setting>
   );
 }
 
@@ -43,8 +43,8 @@ export function ResetSync({ resetSync }) {
   }
 
   return (
-    <ButtonSetting
-      button={
+    <Setting
+      primaryAction={
         <ButtonWithLoading loading={resetting} onClick={onResetSync}>
           Reset sync
         </ButtonWithLoading>
@@ -56,6 +56,6 @@ export function ResetSync({ resetSync }) {
         on other devices will have to be re-downloaded to use the new sync ID.
         Use this if there is a problem with syncing and you want to start fresh.
       </Text>
-    </ButtonSetting>
+    </Setting>
   );
 }
diff --git a/packages/desktop-client/src/components/settings/UI.js b/packages/desktop-client/src/components/settings/UI.js
index 1a9ea6d46..03aaf870d 100644
--- a/packages/desktop-client/src/components/settings/UI.js
+++ b/packages/desktop-client/src/components/settings/UI.js
@@ -24,7 +24,7 @@ export function Section({ title, children, style, titleProps, ...props }) {
   );
 }
 
-export function ButtonSetting({ button, children }) {
+export function Setting({ primaryAction, children }) {
   return (
     <View
       {...css(
@@ -43,11 +43,16 @@ export function ButtonSetting({ button, children }) {
       )}
     >
       <View
-        style={{ marginBottom: 10, maxWidth: 500, lineHeight: 1.5, gap: 10 }}
+        style={{
+          marginBottom: primaryAction ? 10 : 0,
+          maxWidth: 500,
+          lineHeight: 1.5,
+          gap: 10
+        }}
       >
         {children}
       </View>
-      {button}
+      {primaryAction || null}
     </View>
   );
 }
diff --git a/packages/desktop-client/src/components/settings/index.js b/packages/desktop-client/src/components/settings/index.js
index da545a46f..f9450518e 100644
--- a/packages/desktop-client/src/components/settings/index.js
+++ b/packages/desktop-client/src/components/settings/index.js
@@ -16,6 +16,7 @@ import useServerVersion from '../../hooks/useServerVersion';
 import { isMobile } from '../../util';
 import { Page } from '../Page';
 import EncryptionSettings from './Encryption';
+import ExperimentalFeatures from './Experimental';
 import ExportBudget from './Export';
 import FixSplitsTool from './FixSplits';
 import FormatSettings from './Format';
@@ -109,6 +110,7 @@ function Settings({
             <ResetCache />
             <ResetSync resetSync={resetSync} />
             <FixSplitsTool />
+            <ExperimentalFeatures prefs={prefs} savePrefs={savePrefs} />
           </AdvancedToggle>
         </View>
       </Page>
-- 
GitLab