Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Profile page / Follower + Following #49

Merged
merged 10 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/db/migrations/1_USER_V1.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ VALUES
('user_2chL8dX6HdbBAuvu3DDM9f9NzKK', 'Ania', 'Misiorek', 'ania', 'https://ca.slack-edge.com/T2CHL6FEG-U05QP4M4M3P-349be7323f07-512'),
('user_2cpFbBLPGkPbszijtQneek7ZJxg', 'Leroy', 'Shaigorodsky', 'leroy', 'https://ca.slack-edge.com/T2CHL6FEG-U040ST08HM1-c3d453828123-512'),
('user_2dv5XFsCMYc4qLcsAnEJ1aUbxnk', 'Cam', 'Plume', 'campd10', 'https://ca.slack-edge.com/T2CHL6FEG-U06DCDZ3FB8-1c488c509f95-512'),
('user_2cwGfu9zcjsbxq5Lp8gy2rkVNlc', 'Nathan', 'Jung', 'nathan', 'https://ca.slack-edge.com/T2CHL6FEG-U05QL55RDBQ-8fd6c3499cac-512'),
('user_2fFVsSf4viW9pjx6Sd5dxEgutJ1', 'Nathan', 'Jung', 'nathan', 'https://ca.slack-edge.com/T2CHL6FEG-U05QL55RDBQ-8fd6c3499cac-512'),
('user_2dvSY6HpxotzWCfch5m4a4OVpAK', 'Aryan', 'Kale', 'aryankale', 'https://cdn-icons-png.freepik.com/256/552/552848.png');

4 changes: 3 additions & 1 deletion backend/src/db/migrations/2_FOLLOWING_V1.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ INSERT INTO followings (follower_user_id, following_user_id)
VALUES
('user_2chL8dX6HdbBAuvu3DDM9f9NzKK', 'user_2cpFbBLPGkPbszijtQneek7ZJxg'),
('user_2chL8dX6HdbBAuvu3DDM9f9NzKK', 'user_2dv5XFsCMYc4qLcsAnEJ1aUbxnk'),
('user_2cpFbBLPGkPbszijtQneek7ZJxg', 'user_2chL8dX6HdbBAuvu3DDM9f9NzKK');
('user_2cpFbBLPGkPbszijtQneek7ZJxg', 'user_2chL8dX6HdbBAuvu3DDM9f9NzKK'),
('user_2fFVsSf4viW9pjx6Sd5dxEgutJ1', 'user_2dv5XFsCMYc4qLcsAnEJ1aUbxnk'),
('user_2dv5XFsCMYc4qLcsAnEJ1aUbxnk', 'user_2fFVsSf4viW9pjx6Sd5dxEgutJ1');
2 changes: 1 addition & 1 deletion backend/src/db/migrations/5_USER_PORTFOLIO_V1.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ VALUES
('user_2chL8dX6HdbBAuvu3DDM9f9NzKK', 130, 14, 680, 93),
('user_2cpFbBLPGkPbszijtQneek7ZJxg', -14, -8, 680, 93),
('user_2dv5XFsCMYc4qLcsAnEJ1aUbxnk', 400, 3, 680, 93),
('user_2cwGfu9zcjsbxq5Lp8gy2rkVNlc', 200, 9, 680, 93);
('user_2fFVsSf4viW9pjx6Sd5dxEgutJ1', 200, 9, 680, 93);
2 changes: 1 addition & 1 deletion backend/src/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package models
import "time"

type User struct {
ID string `gorm:"column:id;primaryKey;"`
ID string `gorm:"column:id;primaryKey;" json:"id"`
CreatedAt time.Time `json:"created_at" example:"2023-09-20T16:34:50Z"`
UpdatedAt time.Time `json:"updated_at" example:"2023-09-20T16:34:50Z"`
FirstName string `gorm:"type:varchar(255)" json:"first_name"`
Expand Down
4 changes: 4 additions & 0 deletions backend/src/services/etrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"backend/src/models"
"backend/src/types"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -326,6 +327,9 @@ func getETradePortfolio(client *http.Client, tokens *models.OAuthTokens, account
func (s *ETradeService) GetUserPortfolio(userID string) (models.UserPortfolio, error) {
var portfolio models.UserPortfolio
if err := s.DB.Preload("Positions").Where("user_id = ?", userID).First(&portfolio).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return models.UserPortfolio{}, nil
}
return models.UserPortfolio{}, fmt.Errorf("error retrieving all positions from the database: %s", err)
}

Expand Down
119 changes: 74 additions & 45 deletions frontend/components/ProfileBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,115 @@
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native'
import {
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
Pressable,
} from 'react-native';
// import { theme } from '../../theme'
import React from 'react'
import React, { useEffect, useState } from 'react';
// import ActionButton from '../ActionButton'
// import { useNavigation } from '@react-navigation/native'
import ProfileBio from './ProfileBio'
// import { useSession } from '@clerk/clerk-expo'
import ProfileBio from './ProfileBio';
import { useNavigation } from '@react-navigation/native';
import { AuthNavigationProp } from '../types/navigationTypes';
import { getUserFollowers, getUserFollowing } from '../services/followers';
import { User } from '../types/types';
import { useSession } from '@clerk/clerk-expo';

interface ProfileBannerProps {
user?: string
user: User;
}

const ProfileBanner = ({ user }: ProfileBannerProps) => {
/*
const { session } = useSession()
const navigation = useNavigation()
const { session } = useSession();
const navigation = useNavigation<AuthNavigationProp>();
const [following, setFollowing] = useState<User[]>([]);
const [followers, setFollowers] = useState<User[]>([]);

const navigateToEditProfile = () => {
navigation.navigate({ name: "Edit My Profile" })
}
*/
const navigateToFollowers = () => {
navigation.navigate('Followers', { label: 'Followers', users: followers });
};

return (
<View className='flex flex-col px-4 mb-2'>
const navigateToFollowing = () => {
navigation.navigate('Followers', { label: 'Followers', users: following });
};

useEffect(() => {
getUserFollowers(user.id).then(users => {
setFollowers(users);
});
getUserFollowing(user.id).then(users => {
setFollowing(users);
});
}, []);

<View className='flex flex-row items-center justify-between gap-1 mb-4 mt-3'>
console.log(user);

return (
<View className="flex flex-col px-4 mb-2">
<View className="flex flex-row items-center justify-between gap-1 mb-4 mt-3">
<Image
// must be a perfect circle
className='w-32 h-32'
className="w-32 h-32"
style={profileStyles.profileImage}
source={{ uri: "currentAuth?.photoURL" }}
source={{ uri: session?.user.imageUrl }}
/>

<View className='flex flex-col items-center flex-1 gap-2'>
<View className='flex flex-row justify-evenly flex-1' >

<View className='flex flex-col items-center px-4 py-2'>
<Text className='text-sm font-semibold'>10</Text>
<Text className='text-sm font-semibold'>
Followers
<View className="flex flex-col items-center flex-1 gap-2">
<View className="flex flex-row justify-evenly flex-1">
<Pressable
className="flex flex-col items-center px-4 py-2"
onPress={navigateToFollowers}>
<Text className="text-sm font-semibold">
{followers?.length | 0}
</Text>
</View>
<Text className="text-sm font-semibold">Followers</Text>
</Pressable>

<View className='flex flex-col items-center px-4 py-2'>
<Text className='text-sm font-semibold'>{10}</Text>
<Text adjustsFontSizeToFit={true} numberOfLines={1} className='text-sm font-semibold'>
<Pressable
className="flex flex-col items-center px-4 py-2"
onPress={navigateToFollowing}>
<Text className="text-sm font-semibold">
{following?.length | 0}
</Text>
<Text
adjustsFontSizeToFit={true}
numberOfLines={1}
className="text-sm font-semibold">
Following
</Text>
</View>
</Pressable>
</View>

<TouchableOpacity
className='flex items-center justify-center flex-1 mb-5 w-48'
style={profileStyles.followButton}
onPress={() => console.log(user)}
>
<Text className='font-semibold text-[#02AD98]'>Edit Profile</Text>
className="flex items-center justify-center flex-1 mb-5 w-48"
style={profileStyles.followButton}>
<Text className="font-semibold text-[#02AD98]">Edit Profile</Text>
</TouchableOpacity>
</View>
</View>

<ProfileBio
fullName="first_name last_name"
fullName={`${user.first_name || session?.user.firstName} ${user.last_name || session?.user.lastName}`}
description="profile description? Lorem ipsum dolor sit amet, consectetur adipiscing elit.Praesent vel nisi sed diam ultricies viverra sit amet nec dolor...."
/>

</View>
)
}

export default ProfileBanner
);
};

export default ProfileBanner;

const profileStyles = StyleSheet.create({
profileImage: {
borderRadius: 180,
borderColor: "black",
borderWidth: 2,
borderColor: '#CCCCCC',
borderWidth: 1,
},
followButton: {
backgroundColor: "rgba(2, 173, 152, 0.18)",
backgroundColor: 'rgba(2, 173, 152, 0.18)',
paddingVertical: 5,
paddingHorizontal: 20,
borderRadius: 5
borderRadius: 5,
},
})
});
25 changes: 25 additions & 0 deletions frontend/components/UserItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { View, Text } from 'react-native'
import React from 'react'
import { Icon } from '@rneui/themed';
import { User } from '../types/types';

interface UserItemProps {
user: User;
}

const UserItem = ({ user }: UserItemProps) => {
return (
<View className='flex flex-row items-center p-4'>
<View className='flex flex-row items-center flex-1 space-x-3'>
<Icon type='material-community' name="account" size={30} color='white' backgroundColor="#D5D5D5" style={{
padding: 2
}} />
<View className='flex flex-col flex-1 pr-3'>
<Text className='font-medium' >{user.username}</Text>
</View>
</View>
</View>
)
}

export default UserItem
4 changes: 2 additions & 2 deletions frontend/pages/AuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {useEffect, useState} from 'react'
import {RefreshControl, ScrollView} from "nativewind/dist/preflight";
import ETradeAuth from "../components/ETradeAuth";
import {TokenStatus} from "../types/types";
import {getTokenStatus} from "../services/users";
import {getTokenStatus} from "../services/etrade";

const AuthPage = () => {
const [authenticated, setAuthenticated] = useState(false)
Expand All @@ -15,7 +15,7 @@ const AuthPage = () => {
}

const getETradeTokenStatus = async () => {
const callback: TokenStatus = await getTokenStatus(2);
const callback: TokenStatus = await getTokenStatus('user_2chL8dX6HdbBAuvu3DDM9f9NzKK');
if (callback.status === "active") {
setAuthenticated(true)
} else {
Expand Down
44 changes: 44 additions & 0 deletions frontend/pages/Followers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { FlatList, Pressable } from 'react-native';
import React, { useEffect } from 'react';
import { Icon } from '@rneui/themed';
import { useNavigation, useRoute } from '@react-navigation/native';
import { FollowerRouteParams, User } from '../types/types';
import UserItem from '../components/UserItem';
import { AuthNavigationProp } from '../types/navigationTypes';

const Followers = () => {
const navigation = useNavigation<AuthNavigationProp>();
const route = useRoute();
const users = (route.params as FollowerRouteParams)?.users;

useEffect(() => {
// set the title of the page
navigation.setOptions({
headerShown: true,
headerTitle: 'Followers',
headerTitleAlign: 'center',
headerLeft: () => (
<Pressable onPress={() => navigation.goBack()}>
<Icon type="material-community" name="chevron-left" size={30} color="black" style={{ paddingLeft: 5 }} />
</Pressable>
),
});
}, []);

const navigateToProfile = (user: User) => {
navigation.navigate('FollowerProfile', { user: user })
};

return (
<FlatList
data={users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable onPress={() => navigateToProfile(item)}>
<UserItem user={item} />
</Pressable>
)} />
);
};

export default Followers;
28 changes: 13 additions & 15 deletions frontend/pages/Profile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useNavigation } from '@react-navigation/native';
import { useNavigation, useRoute } from '@react-navigation/native';
import React, { useEffect, useState } from 'react';
import { FlatList, Pressable, ScrollView, View } from 'react-native';
import { useSession } from '@clerk/clerk-expo';
Expand All @@ -10,37 +10,34 @@ import { ProfileActivityData } from '../constants';
import ProfilePerformance from '../components/ProfilePerformance';
import SignOut from '../components/SignOutButton';
import { getPortoflio } from '../services/etrade';
import { UserPortfolio } from '../types/types';
import { ProfileRouteParams, UserPortfolio } from '../types/types';
import { ProfilePositions } from '../components/ProfilePositions';
// import SettingsSvg from '../assets/SettingsIcon.svg';



const Profile = () => {
const { session } = useSession();
const navigation = useNavigation();
const [isPortfolioSelected, setIsPortfolioSelected] = useState<boolean>(true);
const [isActivitySelected, setIsActivitySelected] = useState<boolean>(false);
const [pageNumber, setPageNumber] = useState<number>(0);
const [portfolio, setPortfolio] = useState<UserPortfolio>()
const route = useRoute()
const isFollowerProfile = (route.params as ProfileRouteParams)?.user !== undefined;
const user = (route.params as ProfileRouteParams)?.user || session!.user!;

const OnActivitySelected = () => {
setIsPortfolioSelected(false);
setIsActivitySelected(true);
setPageNumber(1)
console.log(`Activity selected: ${isActivitySelected}`);
}

const OnPortfolioSelected = () => {
setIsPortfolioSelected(true);
setIsActivitySelected(false);
setPageNumber(0)
console.log(`Portfolio selected: ${isPortfolioSelected}`);
}

useEffect(() => {
// set the title of the page
navigation.setOptions({
headerShown: true,
headerTitle: `@${session?.user.username}`,
headerTitle: `@${user.username}`,
headerTitleAlign: 'center',
headerRight: () => (
<Icon type='material-community' name='cog' size={30} color='black' style={{paddingRight: 10}} />
Expand All @@ -53,7 +50,6 @@ const Profile = () => {
})

return navigation.addListener('focus', () => {
console.log(`Profile Page | session token: ${session?.getToken()}`);
if (session?.user.username === undefined) {
/* Unsure why casting to never is required, issue to look into */
navigation.navigate('Signin' as never);
Expand All @@ -62,19 +58,21 @@ const Profile = () => {
}, []);

useEffect(() => {
getPortoflio("user_2ceWSEk1tU7bByPHmtwsla94w7e").then(userPortfolio => {
getPortoflio(user.id).then(userPortfolio => {
setPortfolio(userPortfolio)
})
}, []);

return (
<ScrollView className='bg-white'>
<View className='flex flex-col space-y-2'>
<ProfileBanner />
<ProfileBanner user={user}/>

<View className='flex flex-row'>
<SubTabButton title='Portfolio' selected={pageNumber == 0} onPress={OnPortfolioSelected} />
<SubTabButton title='Activity' selected={pageNumber == 1} onPress={OnActivitySelected} />
{!isFollowerProfile && (
<SubTabButton title='Activity' selected={pageNumber == 1} onPress={OnActivitySelected} />
)}
</View>

<ScrollView
Expand Down
2 changes: 1 addition & 1 deletion frontend/reducers/onboarding/onboardingReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const onboardingSlice = createSlice({
financialGoalsShortTerm: [],
financialGoalsLongTerm: [],
financialLiteracy: [],
isOnboarding: 'normal', // 'onboarding', 'normal', 'makingPost'
isOnboarding: 'onboarding', // 'onboarding', 'normal', 'makingPost'
},
reducers: {
updateFirstName(state, action) {
Expand Down
Loading
Loading