From 30853cbf242c5805ae9ebd659550607afbbcfdcc Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 17 Apr 2024 18:10:00 -0400 Subject: [PATCH] copy page, copy services in frontend --- backend/src/routes/user.go | 16 -- frontend/components/ProfilePerformance.tsx | 15 +- frontend/pages/Copy/CopyTradesPage.tsx | 184 +++++++++++++++++++++ frontend/router/BottomNavBar.tsx | 8 +- frontend/router/ProfileNavigation.tsx | 17 +- frontend/services/copy.ts | 10 ++ frontend/services/users.ts | 24 +-- frontend/types/navigationTypes.ts | 9 + 8 files changed, 233 insertions(+), 50 deletions(-) create mode 100644 frontend/pages/Copy/CopyTradesPage.tsx create mode 100644 frontend/services/copy.ts diff --git a/backend/src/routes/user.go b/backend/src/routes/user.go index c9e35939..671dc494 100644 --- a/backend/src/routes/user.go +++ b/backend/src/routes/user.go @@ -15,22 +15,6 @@ func SetupUserRoutes(router *gin.Engine, db *gorm.DB, clerkClient clerk.Client) userRoutes := router.Group("/users") { - /* Protected Routes */ - - /* - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - Clerk middleware currently doesn't work since ngrok does not allow us to - edit request headers. - - We can: - - implement our own clerk middleware - - find out how to modify request headers with axios and bypass ngrok - - find out how to modify request headers with ngrok options dynamically - - add middleware to filter all requests and attach headers once they reach the backend (might conflict with clerk) - - */ - // SetupAuthMiddleware(clerkClient, router) - // Routes that only read/write DB userRoutes.GET("", userController.GetAllUsers) userRoutes.POST("", userController.CreateUser) diff --git a/frontend/components/ProfilePerformance.tsx b/frontend/components/ProfilePerformance.tsx index 5189ec6a..a909f65f 100644 --- a/frontend/components/ProfilePerformance.tsx +++ b/frontend/components/ProfilePerformance.tsx @@ -1,11 +1,20 @@ import { View, Text, Pressable } from 'react-native' import React from 'react' +import { useNavigation } from '@react-navigation/native'; +import { ProfileNavigationProp } from '../types/navigationTypes'; +import { useSession } from '@clerk/clerk-expo'; interface ProfilePerformanceProps { portfolioValue: number } const ProfilePerformance = ({portfolioValue} : ProfilePerformanceProps) => { + const { session } = useSession(); + const navigator = useNavigation(); + const handleCopyTrades = () => { + navigator.navigate('CopyTrades') + } + return ( Performance @@ -13,11 +22,11 @@ const ProfilePerformance = ({portfolioValue} : ProfilePerformanceProps) => { = 0 ? 'text-[#02AD98]' : 'text-[#FF2B51]'}`}> {portfolioValue} % - - Copy Trades - YTD Performance + {(session?.user.username == 'nathan') ? + Copy Trades + : null} ) } diff --git a/frontend/pages/Copy/CopyTradesPage.tsx b/frontend/pages/Copy/CopyTradesPage.tsx new file mode 100644 index 00000000..7e7f13c5 --- /dev/null +++ b/frontend/pages/Copy/CopyTradesPage.tsx @@ -0,0 +1,184 @@ +import React, { useEffect, useState } from 'react'; +import { + View, + Text, + TextInput, + Switch, + TouchableOpacity, + StyleSheet, + Alert, +} from 'react-native'; +import { useNavigation } from '@react-navigation/native'; +import { useSession } from '@clerk/clerk-expo'; +import { + ProfileNavigationProp, + AuthNavigationProp, +} from '../../types/navigationTypes'; +import { User } from '../../types/types'; +import { getUserById } from '../../services/users'; +import { copyTrades } from '../../services/copy'; + +function CopyTradesPage() { + const { session } = useSession(); + const navigation = useNavigation(); + const authNavigation = useNavigation(); + const [investmentAmount, setInvestmentAmount] = useState('1000'); + const [stopLossEnabled, setStopLossEnabled] = useState(false); + const [stopLossAmount, setStopLossAmount] = useState('500'); + const [tradeAuthorizationRequired, setTradeAuthorizationRequired] = + useState(false); + const [user, setUser] = useState(null); + + const handleSubmit = async () => { + if (!user) { + Alert.alert('Error', "User doesn't exist"); + return; + } + if (!session?.user.id) { + authNavigation.navigate('Login'); + return; + } + + try { + await copyTrades(session?.user.id as string, user.username); + } catch (error) { + Alert.alert('Error', 'Failed to copy trades'); + return; + } + + Alert.alert('Success', 'Trades copied successfully'); + navigation.navigate('Profile'); + }; + + useEffect(() => { + const fetchUser = async () => { + try { + const user = await getUserById(''); + setUser(user); + } catch (error) { + console.error('Error fetching user:', error); + } + }; + + fetchUser(); + }, []); + + return ( + + + @kevinkevindaliri + navigation.navigate('Profile')}> + × + + + + ACCT 123456 + {/* */} + + + Investment Amount + + + This investment will proportionally copy this investor’s portfolio. + + + + Set stop loss + + + {stopLossEnabled && ( + + Investment Falls Below + + + )} + + Require trade authorization + + + + Copy Trades + + + ); +} + +const styles = StyleSheet.create({ + container: { + padding: 16, + backgroundColor: '#FFF', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 24, + }, + username: { + fontWeight: 'bold', + }, + closeButton: { + fontSize: 24, + }, + accountInfo: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 12, + backgroundColor: '#F5F5F5', + borderRadius: 8, + marginBottom: 16, + }, + accountLabel: { + fontWeight: 'bold', + }, + // potentially add arrowIcon styling if using an Image component + inputGroup: { + marginBottom: 16, + }, + label: { + marginBottom: 8, + }, + input: { + borderWidth: 1, + borderColor: '#E0E0E0', + padding: 12, + borderRadius: 8, + marginBottom: 8, + }, + smallText: { + color: '#757575', + fontSize: 12, + }, + switchContainer: { + marginBottom: 24, + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'space-between', + }, + submitButton: { + backgroundColor: '#4CAF50', + borderRadius: 8, + padding: 16, + alignItems: 'center', + }, + submitButtonText: { + color: 'white', + fontWeight: 'bold', + }, +}); + +export default CopyTradesPage; diff --git a/frontend/router/BottomNavBar.tsx b/frontend/router/BottomNavBar.tsx index 0b160e30..2d5f8d8c 100644 --- a/frontend/router/BottomNavBar.tsx +++ b/frontend/router/BottomNavBar.tsx @@ -48,7 +48,7 @@ const BottomNavBar = () => { options={{ tabBarActiveTintColor: '#02AD98', tabBarShowLabel: false, - tabBarInactiveTintColor: '#333333' + tabBarInactiveTintColor: '#333333', }} /> { options={{ tabBarActiveTintColor: '#02AD98', tabBarShowLabel: false, - tabBarInactiveTintColor: '#333333' + tabBarInactiveTintColor: '#333333', }} /> { options={{ tabBarActiveTintColor: '#02AD98', tabBarShowLabel: false, - tabBarInactiveTintColor: '#333333' + tabBarInactiveTintColor: '#333333', }} /> ); }; + + export default BottomNavBar; \ No newline at end of file diff --git a/frontend/router/ProfileNavigation.tsx b/frontend/router/ProfileNavigation.tsx index 4ce905a1..3bf1e139 100644 --- a/frontend/router/ProfileNavigation.tsx +++ b/frontend/router/ProfileNavigation.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import Profile from '../pages/Profile'; import Followers from '../pages/Followers'; +import CopyTradesPage from '../pages/Copy/CopyTradesPage'; const Stack = createStackNavigator(); @@ -10,18 +11,10 @@ const ProfileNavigator = () => { return ( {} - - - + + + + ); }; diff --git a/frontend/services/copy.ts b/frontend/services/copy.ts new file mode 100644 index 00000000..7131449f --- /dev/null +++ b/frontend/services/copy.ts @@ -0,0 +1,10 @@ +import axios, { AxiosResponse } from 'axios'; +import { API_LINK } from './CommonDocs'; +import { Redirect } from '../types/types'; + +export const copyTrades = async (currentUserId: string, targetUserId: string) => { + const response: AxiosResponse = await axios.post( + `http://${API_LINK}/portfolio/${currentUserId}/${targetUserId}` + ); + return response.data; +}; diff --git a/frontend/services/users.ts b/frontend/services/users.ts index 9d3ae71e..94a72767 100644 --- a/frontend/services/users.ts +++ b/frontend/services/users.ts @@ -1,6 +1,6 @@ import axios, { AxiosResponse } from 'axios'; import { API_LINK } from './CommonDocs'; -import { FinancialGoal, Post, TokenStatus, User } from '../types/types'; +import { FinancialGoal, User } from '../types/types'; export const getAllUsers = async (): Promise => { console.log(API_LINK); @@ -10,21 +10,6 @@ export const getAllUsers = async (): Promise => { return response.data; }; -export const getPosts = async (): Promise => { - const response: AxiosResponse = await axios.get( - `http://${API_LINK}/posts`, - ); - - return response.data; -}; - -export const getTokenStatus = async (id: number): Promise => { - const response: AxiosResponse = await axios.get( - `http://${API_LINK}/etrade/status/${id}`, - ); - return response.data; -}; - export const registerUser = async ( user: User, shortTermGoals: Array, @@ -40,3 +25,10 @@ export const registerUser = async ( ); return response.data; }; + +export const getUserById = async (id: string): Promise => { + const response: AxiosResponse = await axios.get( + `http://${API_LINK}/users/${id}`, + ); + return response.data; +}; \ No newline at end of file diff --git a/frontend/types/navigationTypes.ts b/frontend/types/navigationTypes.ts index 07afc7e1..59e46969 100644 --- a/frontend/types/navigationTypes.ts +++ b/frontend/types/navigationTypes.ts @@ -26,6 +26,11 @@ export type RootStackParamList = { // MainApp: undefined; }; +export type ProfileStackParamList = { + Profile: undefined; + CopyTrades: undefined; +}; + export type AuthNavigationProp = StackNavigationProp< RootStackParamList >; @@ -35,6 +40,10 @@ export type LevelPageNavigationProp = StackNavigationProp< 'LevelPage' >; +export type ProfileNavigationProp = StackNavigationProp< + ProfileStackParamList +>; + // export type ConnectPageNavigationProp = StackNavigationProp< // RootStackParamList, // 'ConnectPage'