about summary refs log tree commit diff
path: root/src/view/com/modals/AddAppPasswords.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals/AddAppPasswords.tsx')
-rw-r--r--src/view/com/modals/AddAppPasswords.tsx216
1 files changed, 216 insertions, 0 deletions
diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx
new file mode 100644
index 000000000..1d2f80ff0
--- /dev/null
+++ b/src/view/com/modals/AddAppPasswords.tsx
@@ -0,0 +1,216 @@
+import React, {useState} from 'react'
+import {StyleSheet, TextInput, View, TouchableOpacity} from 'react-native'
+import {Text} from '../util/text/Text'
+import {Button} from '../util/forms/Button'
+import {s} from 'lib/styles'
+import {useStores} from 'state/index'
+import {usePalette} from 'lib/hooks/usePalette'
+import {isDesktopWeb} from 'platform/detection'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
+import Clipboard from '@react-native-clipboard/clipboard'
+import * as Toast from '../util/Toast'
+
+export const snapPoints = ['70%']
+
+const shadesOfBlue: string[] = [
+  'AliceBlue',
+  'Aqua',
+  'Aquamarine',
+  'Azure',
+  'BabyBlue',
+  'Blue',
+  'BlueViolet',
+  'CadetBlue',
+  'CornflowerBlue',
+  'Cyan',
+  'DarkBlue',
+  'DarkCyan',
+  'DarkSlateBlue',
+  'DeepSkyBlue',
+  'DodgerBlue',
+  'ElectricBlue',
+  'LightBlue',
+  'LightCyan',
+  'LightSkyBlue',
+  'LightSteelBlue',
+  'MediumAquaMarine',
+  'MediumBlue',
+  'MediumSlateBlue',
+  'MidnightBlue',
+  'Navy',
+  'PowderBlue',
+  'RoyalBlue',
+  'SkyBlue',
+  'SlateBlue',
+  'SteelBlue',
+  'Teal',
+  'Turquoise',
+]
+
+export function Component({}: {}) {
+  const pal = usePalette('default')
+  const store = useStores()
+  const [name, setName] = useState(
+    shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)],
+  )
+  const [appPassword, setAppPassword] = useState<string>()
+  const [wasCopied, setWasCopied] = useState(false)
+
+  const onCopy = React.useCallback(() => {
+    if (appPassword) {
+      Clipboard.setString(appPassword)
+      Toast.show('Copied to clipboard')
+      setWasCopied(true)
+    }
+  }, [appPassword])
+
+  const onDone = React.useCallback(() => {
+    store.shell.closeModal()
+  }, [store])
+
+  const createAppPassword = async () => {
+    try {
+      const newPassword = await store.me.createAppPassword(name)
+      if (newPassword) {
+        setAppPassword(newPassword.password)
+      } else {
+        Toast.show('Failed to create app password.')
+        // TODO: better error handling (?)
+      }
+    } catch (e) {
+      Toast.show('Failed to create app password.')
+      store.log.error('Failed to create app password', {e})
+    }
+  }
+
+  return (
+    <View style={[styles.container, pal.view]} testID="addAppPasswordsModal">
+      <View>
+        {!appPassword ? (
+          <Text type="lg">
+            Please enter a unique name for this App Password. We have generated
+            a random name for you.
+          </Text>
+        ) : (
+          <Text type="lg">
+            <Text type="lg-bold">Here is your app password.</Text> Use this to
+            sign into the other app along with your handle.
+          </Text>
+        )}
+        {!appPassword ? (
+          <View style={[pal.btn, styles.textInputWrapper]}>
+            <TextInput
+              style={[styles.input, pal.text]}
+              onChangeText={setName}
+              value={name}
+              placeholder="Enter a name for this App Password"
+              placeholderTextColor={pal.colors.textLight}
+              autoCorrect={false}
+              autoComplete="off"
+              autoCapitalize="none"
+              autoFocus={true}
+              selectTextOnFocus={true}
+              multiline={true} // need this to be true otherwise selectTextOnFocus doesn't work
+              numberOfLines={1} // hack for multiline so only one line shows (android)
+              scrollEnabled={false} // hack for multiline so only one line shows (ios)
+              blurOnSubmit={true} // hack for multiline so it submits
+              editable={!appPassword}
+              returnKeyType="done"
+              onEndEditing={createAppPassword}
+            />
+          </View>
+        ) : (
+          <TouchableOpacity
+            style={[pal.border, styles.passwordContainer, pal.btn]}
+            onPress={onCopy}>
+            <Text type="2xl-bold">{appPassword}</Text>
+            {wasCopied ? (
+              <Text style={[pal.textLight]}>Copied</Text>
+            ) : (
+              <FontAwesomeIcon
+                icon={['far', 'clone']}
+                style={pal.text as FontAwesomeIconStyle}
+                size={18}
+              />
+            )}
+          </TouchableOpacity>
+        )}
+      </View>
+      {appPassword ? (
+        <Text type="lg" style={[pal.textLight, s.mb10]}>
+          For security reasons, you won't be able to view this again. If you
+          lose this password, you'll need to generate a new one.
+        </Text>
+      ) : null}
+      <View style={styles.btnContainer}>
+        <Button
+          type="primary"
+          label={!appPassword ? 'Create App Password' : 'Done'}
+          style={styles.btn}
+          labelStyle={styles.btnLabel}
+          onPress={!appPassword ? createAppPassword : onDone}
+        />
+      </View>
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    paddingBottom: isDesktopWeb ? 0 : 50,
+    marginHorizontal: 16,
+  },
+  textInputWrapper: {
+    borderRadius: 8,
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginTop: 16,
+    marginBottom: 8,
+  },
+  input: {
+    flex: 1,
+    width: '100%',
+    paddingVertical: 10,
+    paddingHorizontal: 8,
+    marginTop: 6,
+    fontSize: 17,
+    letterSpacing: 0.25,
+    fontWeight: '400',
+    borderRadius: 10,
+  },
+  passwordContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    paddingVertical: 8,
+    paddingHorizontal: 16,
+    alignItems: 'center',
+    borderRadius: 10,
+    marginTop: 16,
+    marginBottom: 12,
+  },
+  btnContainer: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    marginTop: 12,
+  },
+  btn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderRadius: 32,
+    paddingHorizontal: 60,
+    paddingVertical: 14,
+  },
+  btnLabel: {
+    fontSize: 18,
+  },
+  groupContent: {
+    borderTopWidth: 1,
+    flexDirection: 'row',
+    alignItems: 'center',
+  },
+})