1import * as React from 'react'
2import * as Kb from '../../../common-adapters'
3import * as Styles from '../../../styles'
4import {ParticipantsRow} from '../../common'
5import {isLargeScreen} from '../../../constants/platform'
6import {SelectedEntry, DropdownEntry, DropdownText} from './dropdown'
7import Search from './search'
8import {Account} from '.'
9import debounce from 'lodash/debounce'
10import defer from 'lodash/defer'
11
12export type ToKeybaseUserProps = {
13  isRequest: boolean
14  recipientUsername: string
15  errorMessage?: string
16  onShowProfile: (username: string) => void
17  onRemoveProfile: () => void
18  onChangeRecipient: (recipient: string) => void
19  onScanQRCode: (() => void) | null
20  onSearch: () => void
21}
22
23const placeholderExample = isLargeScreen ? 'Ex: G12345... or you*example.com' : 'G12.. or you*example.com'
24
25const ToKeybaseUser = (props: ToKeybaseUserProps) => {
26  if (props.recipientUsername) {
27    // A username has been set, so display their name and avatar.
28    return (
29      <ParticipantsRow
30        heading={props.isRequest ? 'From' : 'To'}
31        headingAlignment="Left"
32        dividerColor={props.errorMessage ? Styles.globalColors.red : ''}
33        style={styles.toKeybaseUser}
34      >
35        <Kb.Box2 direction="vertical" fullWidth={true} style={styles.inputBox}>
36          <Kb.Box2 direction="horizontal" centerChildren={true} fullWidth={true}>
37            <Kb.ConnectedNameWithIcon
38              colorFollowing={true}
39              horizontal={true}
40              containerStyle={styles.toKeybaseUserNameWithIcon}
41              username={props.recipientUsername}
42              avatarStyle={styles.avatar}
43              avatarSize={32}
44              onClick="tracker"
45            />
46            <Kb.Icon
47              type="iconfont-remove"
48              boxStyle={styles.keybaseUserRemoveButton}
49              fontSize={16}
50              color={Styles.globalColors.black_20}
51              onClick={props.onRemoveProfile}
52            />
53          </Kb.Box2>
54          {!!props.errorMessage && (
55            <Kb.Text type="BodySmall" style={styles.errorText}>
56              {props.errorMessage}
57            </Kb.Text>
58          )}
59        </Kb.Box2>
60      </ParticipantsRow>
61    )
62  }
63
64  // No username, so show search box.
65  return (
66    <Search
67      heading={props.isRequest ? 'From' : 'To'}
68      onClickResult={props.onChangeRecipient}
69      onSearch={props.onSearch}
70      onShowTracker={props.onShowProfile}
71      onScanQRCode={props.onScanQRCode}
72    />
73  )
74}
75
76export type ToStellarPublicKeyProps = {
77  recipientPublicKey: string
78  errorMessage?: string
79  onChangeRecipient: (recipient: string) => void
80  onScanQRCode: (() => void) | null
81  setReadyToReview: (ready: boolean) => void
82}
83
84const ToStellarPublicKey = (props: ToStellarPublicKeyProps) => {
85  const [recipientPublicKey, setRecipentPublicKey] = React.useState(props.recipientPublicKey)
86  const debouncedOnChangeRecip = React.useCallback(debounce(props.onChangeRecipient, 1e3), [
87    props.onChangeRecipient,
88  ])
89
90  const {setReadyToReview} = props
91  const onChangeRecipient = React.useCallback(
92    (recipientPublicKey: string) => {
93      setRecipentPublicKey(recipientPublicKey)
94      setReadyToReview(false)
95      debouncedOnChangeRecip(recipientPublicKey)
96    },
97    [setReadyToReview, debouncedOnChangeRecip]
98  )
99
100  React.useEffect(() => {
101    if (props.recipientPublicKey !== recipientPublicKey) {
102      // Hot fix to let any empty string textChange callbacks happen before we change the value.
103      defer(() => setRecipentPublicKey(props.recipientPublicKey))
104    }
105    // We do not want this be called when the state changes
106    // Only when the prop.recipientPublicKey changes.
107    // eslint-disable-next-line react-hooks/exhaustive-deps
108  }, [props.recipientPublicKey])
109
110  return (
111    <ParticipantsRow
112      heading="To"
113      headingAlignment="Left"
114      headingStyle={styles.heading}
115      dividerColor={props.errorMessage ? Styles.globalColors.red : ''}
116      style={styles.toStellarPublicKey}
117    >
118      <Kb.Box2 direction="vertical" fullWidth={!Styles.isMobile} style={styles.inputBox}>
119        <Kb.Box2 direction="horizontal" gap="xxtiny" fullWidth={!Styles.isMobile} style={styles.inputInner}>
120          <Kb.Icon
121            sizeType={Styles.isMobile ? 'Small' : 'Default'}
122            type="iconfont-identity-stellar"
123            color={
124              recipientPublicKey.length === 0 || props.errorMessage
125                ? Styles.globalColors.black_20
126                : Styles.globalColors.black
127            }
128          />
129          <Kb.Box2 direction="horizontal" style={styles.publicKeyInputContainer}>
130            <Kb.NewInput
131              type="text"
132              onChangeText={onChangeRecipient}
133              textType="BodySemibold"
134              hideBorder={true}
135              containerStyle={styles.input}
136              multiline={true}
137              rowsMin={2}
138              rowsMax={3}
139              value={recipientPublicKey}
140            />
141            {!recipientPublicKey && (
142              <Kb.Box
143                activeOpacity={1}
144                pointerEvents="none"
145                style={Styles.collapseStyles([Styles.globalStyles.fillAbsolute, styles.placeholderContainer])}
146              >
147                <Kb.Text type="BodySemibold" style={styles.colorBlack20}>
148                  Stellar address
149                </Kb.Text>
150                <Kb.Text type="BodySemibold" style={styles.colorBlack20} lineClamp={1} ellipsizeMode="middle">
151                  {placeholderExample}
152                </Kb.Text>
153              </Kb.Box>
154            )}
155          </Kb.Box2>
156          {!recipientPublicKey && props.onScanQRCode && (
157            <Kb.Icon
158              color={Styles.globalColors.black_50}
159              type="iconfont-qr-code"
160              onClick={props.onScanQRCode}
161              style={styles.qrCode}
162            />
163          )}
164        </Kb.Box2>
165        {!!props.errorMessage && (
166          <Kb.Text type="BodySmall" style={styles.errorText}>
167            {props.errorMessage}
168          </Kb.Text>
169        )}
170      </Kb.Box2>
171    </ParticipantsRow>
172  )
173}
174
175export type ToOtherAccountProps = {
176  user: string
177  toAccount?: Account
178  allAccounts: Account[]
179  onChangeRecipient: (recipient: string) => void
180  onLinkAccount: () => void
181  onCreateNewAccount: () => void
182  showSpinner: boolean
183}
184
185class ToOtherAccount extends React.Component<ToOtherAccountProps> {
186  onAccountDropdownChange = (node: React.ReactNode) => {
187    if (React.isValidElement(node)) {
188      const element: React.ReactElement = node
189      if (element.key === 'create-new') {
190        this.props.onCreateNewAccount()
191      } else if (element.key === 'link-existing') {
192        this.props.onLinkAccount()
193      } else {
194        this.props.onChangeRecipient(element.props.account.id)
195      }
196    }
197  }
198
199  render() {
200    if (this.props.allAccounts.length <= 1) {
201      // A user is sending to another account, but has no other
202      // accounts. Show a "create new account" button.
203      return (
204        <ParticipantsRow heading="To" headingAlignment="Right" style={styles.toAccountRow}>
205          <Kb.Button
206            small={true}
207            type="Wallet"
208            style={styles.createNewAccountButton}
209            label="Create a new account"
210            onClick={this.props.onCreateNewAccount}
211          />
212        </ParticipantsRow>
213      )
214    }
215
216    // A user is sending from an account to another account with other
217    // accounts. Show a dropdown list of other accounts, in addition
218    // to the link existing and create new actions.
219    let items = [
220      <DropdownText
221        spinner={this.props.showSpinner}
222        key="link-existing"
223        text="Link an existing Stellar account"
224      />,
225      <DropdownText spinner={this.props.showSpinner} key="create-new" text="Create a new account" />,
226    ]
227
228    if (this.props.allAccounts.length > 0) {
229      const walletItems = this.props.allAccounts.map(account => (
230        <DropdownEntry key={account.id} account={account} user={this.props.user} />
231      ))
232      items = walletItems.concat(items)
233    }
234
235    return (
236      <ParticipantsRow heading="To" headingAlignment="Right" style={styles.toAccountRow}>
237        <Kb.Dropdown
238          onChanged={this.onAccountDropdownChange}
239          items={items}
240          style={styles.dropdown}
241          selectedBoxStyle={styles.dropdownSelectedBox}
242          selected={
243            this.props.toAccount ? (
244              <SelectedEntry
245                spinner={this.props.showSpinner}
246                account={this.props.toAccount}
247                user={this.props.user}
248              />
249            ) : (
250              <DropdownText
251                spinner={this.props.showSpinner}
252                key="placeholder-select"
253                text="Pick another account"
254              />
255            )
256          }
257        />
258      </ParticipantsRow>
259    )
260  }
261}
262
263const styles = Styles.styleSheetCreate(
264  () =>
265    ({
266      avatar: {
267        marginRight: 8,
268      },
269      colorBlack20: {
270        color: Styles.globalColors.black_20,
271      },
272      createNewAccountButton: Styles.platformStyles({
273        isElectron: {
274          width: 194,
275        },
276      }),
277      dropdown: Styles.platformStyles({
278        isMobile: {height: 32},
279      }),
280      dropdownSelectedBox: Styles.platformStyles({
281        isMobile: {minHeight: 32},
282      }),
283      errorText: Styles.platformStyles({
284        common: {
285          color: Styles.globalColors.redDark,
286          width: '100%',
287        },
288        isElectron: {
289          wordWrap: 'break-word',
290        },
291      }),
292      heading: {
293        alignSelf: 'flex-start',
294      },
295      input: Styles.platformStyles({
296        common: {
297          padding: 0,
298        },
299        isMobile: {
300          paddingLeft: Styles.globalMargins.xtiny,
301        },
302      }),
303      inputBox: Styles.platformStyles({isElectron: {flexGrow: 1}, isMobile: {flex: 1}}),
304      inputInner: Styles.platformStyles({
305        common: {
306          alignItems: 'flex-start',
307          flex: 1,
308          position: 'relative',
309        },
310        isElectron: {
311          flexShrink: 0,
312        },
313      }),
314      keybaseUserRemoveButton: {
315        flex: 1,
316        marginRight: Styles.globalMargins.tiny,
317        textAlign: 'right', // consistent with UserInput
318      },
319      placeholderContainer: Styles.platformStyles({
320        common: {
321          display: 'flex',
322          flexDirection: 'column',
323          paddingLeft: (Styles.isMobile ? 0 : 16) + 4,
324        },
325        isElectron: {
326          pointerEvents: 'none',
327        },
328      }),
329      publicKeyInputContainer: {flexGrow: 1, flexShrink: 1},
330      qrCode: {
331        marginRight: Styles.globalMargins.tiny,
332        marginTop: Styles.globalMargins.tiny,
333      },
334      toAccountRow: Styles.platformStyles({
335        isMobile: {
336          height: 40,
337          paddingBottom: 4,
338          paddingTop: 4,
339        },
340      }),
341      toKeybaseUser: {
342        height: 48,
343      },
344      toKeybaseUserNameWithIcon: {
345        flexGrow: 1,
346      },
347      toStellarPublicKey: {
348        alignItems: 'flex-start',
349        minHeight: 52,
350      },
351    } as const)
352)
353
354export {ToKeybaseUser, ToStellarPublicKey, ToOtherAccount}
355