From f51ad2802556d91ba5036f015c47dd586c3eb958 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Thu, 26 Jan 2023 19:49:16 -0600
Subject: [PATCH] Add right column of web shell and tweak left column

---
 .../com/discover/LiteSuggestedFollows.tsx     | 194 ++++++++++++++++++
 src/view/shell/web/index.tsx                  |  24 +--
 src/view/shell/web/left-column.tsx            |  15 +-
 src/view/shell/web/right-column.tsx           |  43 +++-
 4 files changed, 246 insertions(+), 30 deletions(-)
 create mode 100644 src/view/com/discover/LiteSuggestedFollows.tsx

diff --git a/src/view/com/discover/LiteSuggestedFollows.tsx b/src/view/com/discover/LiteSuggestedFollows.tsx
new file mode 100644
index 00000000..ce01af7c
--- /dev/null
+++ b/src/view/com/discover/LiteSuggestedFollows.tsx
@@ -0,0 +1,194 @@
+import React, {useEffect, useState} from 'react'
+import {
+  ActivityIndicator,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
+import LinearGradient from 'react-native-linear-gradient'
+import {observer} from 'mobx-react-lite'
+import _omit from 'lodash.omit'
+import {ErrorMessage} from '../util/error/ErrorMessage'
+import {Link} from '../util/Link'
+import {Text} from '../util/text/Text'
+import {UserAvatar} from '../util/UserAvatar'
+import * as Toast from '../util/Toast'
+import {useStores} from '../../../state'
+import * as apilib from '../../../state/lib/api'
+import {
+  SuggestedActorsViewModel,
+  SuggestedActor,
+} from '../../../state/models/suggested-actors-view'
+import {s, gradients} from '../../lib/styles'
+import {usePalette} from '../../lib/hooks/usePalette'
+
+export const LiteSuggestedFollows = observer(() => {
+  const store = useStores()
+  const [suggestions, setSuggestions] = useState<SuggestedActor[] | undefined>(
+    undefined,
+  )
+  const [follows, setFollows] = useState<Record<string, string>>({})
+
+  useEffect(() => {
+    const view = new SuggestedActorsViewModel(store)
+    view.loadMore().then(
+      () => {
+        setSuggestions(view.suggestions.slice().sort(randomize).slice(0, 3))
+      },
+      (err: any) => {
+        setSuggestions([])
+        store.log.error('Failed to fetch suggestions', err)
+      },
+    )
+  }, [store, store.log])
+
+  const onPressFollow = async (item: SuggestedActor) => {
+    try {
+      const res = await apilib.follow(store, item.did, item.declaration.cid)
+      setFollows({[item.did]: res.uri, ...follows})
+    } catch (e: any) {
+      store.log.error('Failed fo create follow', e)
+      Toast.show('An issue occurred, please try again.')
+    }
+  }
+  const onPressUnfollow = async (item: SuggestedActor) => {
+    try {
+      await apilib.unfollow(store, follows[item.did])
+      setFollows(_omit(follows, [item.did]))
+    } catch (e: any) {
+      store.log.error('Failed fo delete follow', e)
+      Toast.show('An issue occurred, please try again.')
+    }
+  }
+
+  return (
+    <View>
+      {!suggestions ? (
+        <View>
+          <ActivityIndicator />
+        </View>
+      ) : (
+        <View>
+          {suggestions.map(item => (
+            <Link
+              key={item.did}
+              href={`/profile/${item.handle}`}
+              title={item.displayName || item.handle}>
+              <User
+                item={item}
+                follow={follows[item.did]}
+                onPressFollow={onPressFollow}
+                onPressUnfollow={onPressUnfollow}
+              />
+            </Link>
+          ))}
+        </View>
+      )}
+    </View>
+  )
+})
+
+const User = ({
+  item,
+  follow,
+  onPressFollow,
+  onPressUnfollow,
+}: {
+  item: SuggestedActor
+  follow: string | undefined
+  onPressFollow: (item: SuggestedActor) => void
+  onPressUnfollow: (item: SuggestedActor) => void
+}) => {
+  const pal = usePalette('default')
+  return (
+    <View style={[styles.actor]}>
+      <View style={styles.actorMeta}>
+        <View style={styles.actorAvi}>
+          <UserAvatar
+            size={40}
+            displayName={item.displayName}
+            handle={item.handle}
+            avatar={item.avatar}
+          />
+        </View>
+        <View style={styles.actorContent}>
+          <Text type="lg-medium" style={pal.text} numberOfLines={1}>
+            {item.displayName || item.handle}
+          </Text>
+          <Text type="sm" style={pal.textLight} numberOfLines={1}>
+            @{item.handle}
+          </Text>
+        </View>
+        <View style={styles.actorBtn}>
+          {follow ? (
+            <TouchableOpacity onPress={() => onPressUnfollow(item)}>
+              <View style={[styles.btn, styles.secondaryBtn, pal.btn]}>
+                <Text type="button" style={pal.text}>
+                  Unfollow
+                </Text>
+              </View>
+            </TouchableOpacity>
+          ) : (
+            <TouchableOpacity onPress={() => onPressFollow(item)}>
+              <LinearGradient
+                colors={[gradients.blueLight.start, gradients.blueLight.end]}
+                start={{x: 0, y: 0}}
+                end={{x: 1, y: 1}}
+                style={[styles.btn, styles.gradientBtn]}>
+                <Text type="sm-medium" style={s.white}>
+                  Follow
+                </Text>
+              </LinearGradient>
+            </TouchableOpacity>
+          )}
+        </View>
+      </View>
+    </View>
+  )
+}
+
+function randomize() {
+  return Math.random() > 0.5 ? 1 : -1
+}
+
+const styles = StyleSheet.create({
+  footer: {
+    height: 200,
+    paddingTop: 20,
+  },
+
+  actor: {},
+  actorMeta: {
+    flexDirection: 'row',
+  },
+  actorAvi: {
+    width: 50,
+    paddingTop: 10,
+    paddingBottom: 10,
+  },
+  actorContent: {
+    flex: 1,
+    paddingRight: 10,
+    paddingTop: 10,
+  },
+  actorBtn: {
+    paddingRight: 10,
+    paddingTop: 10,
+  },
+
+  gradientBtn: {
+    paddingHorizontal: 14,
+    paddingVertical: 6,
+  },
+  secondaryBtn: {
+    paddingHorizontal: 8,
+  },
+  btn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingVertical: 7,
+    borderRadius: 50,
+    marginLeft: 6,
+  },
+})
diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx
index 93ae9282..fedc9c3d 100644
--- a/src/view/shell/web/index.tsx
+++ b/src/view/shell/web/index.tsx
@@ -1,10 +1,10 @@
 import React from 'react'
 import {observer} from 'mobx-react-lite'
-import {View, StyleSheet, Text} from 'react-native'
+import {View, StyleSheet} from 'react-native'
 import {useStores} from '../../../state'
 import {match, MatchResult} from '../../routes'
 import {DesktopLeftColumn} from './left-column'
-// import {DesktopRightColumn} from './right-column'
+import {DesktopRightColumn} from './right-column'
 import {Login} from '../../screens/Login'
 import {ErrorBoundary} from '../../com/util/ErrorBoundary'
 import {usePalette} from '../../lib/hooks/usePalette'
@@ -35,6 +35,7 @@ export const WebShell: React.FC = observer(() => {
         </View>
       ))}
       <DesktopLeftColumn />
+      <DesktopRightColumn />
     </View>
   )
   // TODO
@@ -48,25 +49,6 @@ export const WebShell: React.FC = observer(() => {
   //   imagesOpen={store.shell.composerOpts?.imagesOpen}
   //   onPost={store.shell.composerOpts?.onPost}
   // />
-  // return (
-  //   <View style={styles.outerContainer}>
-  //     {store.session.hasSession ? (
-  //       <>
-  //         <DesktopLeftColumn />
-  //         <View style={styles.innerContainer}>
-  //           <Text>Hello, world! (Logged in)</Text>
-  //           {children}
-  //         </View>
-  //         <DesktopRightColumn />
-  //       </>
-  //     ) : (
-  //       <View style={styles.innerContainer}>
-  //         <Text>Hello, world! (Logged out)</Text>
-  //         {children}
-  //       </View>
-  //     )}
-  //   </View>
-  // )
 })
 
 /**
diff --git a/src/view/shell/web/left-column.tsx b/src/view/shell/web/left-column.tsx
index b7309d9c..411b4674 100644
--- a/src/view/shell/web/left-column.tsx
+++ b/src/view/shell/web/left-column.tsx
@@ -42,7 +42,9 @@ export const NavItem = observer(
               </Text>
             )}
           </View>
-          <Text type={isCurrent ? 'xl-bold' : 'xl-medium'}>{label}</Text>
+          <Text type={isCurrent ? 'xl-bold' : 'xl'} style={styles.navItemLabel}>
+            {label}
+          </Text>
         </Link>
       </Pressable>
     )
@@ -86,10 +88,11 @@ export const DesktopLeftColumn = observer(() => {
 const styles = StyleSheet.create({
   container: {
     position: 'absolute',
-    left: 'calc(50vw - 500px)',
-    width: '200px',
+    left: 'calc(50vw - 530px)',
+    width: '230px',
     height: '100%',
     borderRightWidth: 1,
+    paddingTop: 20,
   },
   navItem: {
     padding: '1rem',
@@ -109,7 +112,11 @@ const styles = StyleSheet.create({
     backgroundColor: colors.red3,
     color: colors.white,
     fontSize: 12,
+    fontWeight: 'bold',
     paddingHorizontal: 4,
-    borderRadius: 4,
+    borderRadius: 6,
+  },
+  navItemLabel: {
+    fontSize: 19,
   },
 })
diff --git a/src/view/shell/web/right-column.tsx b/src/view/shell/web/right-column.tsx
index 5fe65cac..2daa16f6 100644
--- a/src/view/shell/web/right-column.tsx
+++ b/src/view/shell/web/right-column.tsx
@@ -1,10 +1,28 @@
 import React from 'react'
-import {Text, View, StyleSheet} from 'react-native'
+import {View, StyleSheet} from 'react-native'
+import {Link} from '../../com/util/Link'
+import {Text} from '../../com/util/text/Text'
+import {usePalette} from '../../lib/hooks/usePalette'
+import {MagnifyingGlassIcon} from '../../lib/icons'
+import {LiteSuggestedFollows} from '../../com/discover/LiteSuggestedFollows'
+import {s} from '../../lib/styles'
 
 export const DesktopRightColumn: React.FC = () => {
+  const pal = usePalette('default')
   return (
-    <View style={styles.container}>
-      <Text>Right Column</Text>
+    <View style={[styles.container, pal.border]}>
+      <Link href="/search" style={[pal.btn, styles.searchContainer]}>
+        <View style={styles.searchIcon}>
+          <MagnifyingGlassIcon style={pal.textLight} />
+        </View>
+        <Text type="lg" style={pal.textLight}>
+          Search
+        </Text>
+      </Link>
+      <Text type="xl-bold" style={s.mb10}>
+        Suggested Follows
+      </Text>
+      <LiteSuggestedFollows />
     </View>
   )
 }
@@ -12,8 +30,23 @@ export const DesktopRightColumn: React.FC = () => {
 const styles = StyleSheet.create({
   container: {
     position: 'absolute',
-    right: 'calc(50vw - 500px)',
-    width: '200px',
+    right: 'calc(50vw - 650px)',
+    width: '350px',
     height: '100%',
+    borderLeftWidth: 1,
+    overscrollBehavior: 'auto',
+    paddingLeft: 30,
+    paddingTop: 10,
+  },
+  searchContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 14,
+    paddingVertical: 10,
+    borderRadius: 20,
+    marginBottom: 20,
+  },
+  searchIcon: {
+    marginRight: 5,
   },
 })