diff --git a/frontend/App.js b/frontend/App.js index dea4562..04e372f 100644 --- a/frontend/App.js +++ b/frontend/App.js @@ -19,6 +19,16 @@ function RootNavigator() { return session ? : ; } +function TestNavigator() { + const { login } = useAuth(); + login("user1@example.com", "password123") + if (isLoading) { + return null; // or some loading screen (maybe we make in future?) + } + return +} + + export default function App() { const [loaded, error] = useFonts({ 'Nunito-Black': require('./assets/fonts/nunito/Nunito-Black.ttf'), diff --git a/frontend/assets/images/Tab.png b/frontend/assets/images/Tab.png new file mode 100644 index 0000000..3f56cac Binary files /dev/null and b/frontend/assets/images/Tab.png differ diff --git a/frontend/assets/images/Tab1.png b/frontend/assets/images/Tab1.png new file mode 100644 index 0000000..07c7e0a Binary files /dev/null and b/frontend/assets/images/Tab1.png differ diff --git a/frontend/assets/images/Tab2.png b/frontend/assets/images/Tab2.png new file mode 100644 index 0000000..46c4003 Binary files /dev/null and b/frontend/assets/images/Tab2.png differ diff --git a/frontend/assets/images/View-Icon.png b/frontend/assets/images/View-Icon.png new file mode 100644 index 0000000..dba45f3 Binary files /dev/null and b/frontend/assets/images/View-Icon.png differ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f23bffb..ead6355 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -46,7 +46,8 @@ "react-test-renderer": "18.1.0", "ts-jest": "^29.2.4", "typescript": "~5.3.3", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "wonka": "^6.3.4" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/frontend/package.json b/frontend/package.json index 3337c4e..a3403c2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,7 +52,8 @@ "react-test-renderer": "18.1.0", "ts-jest": "^29.2.4", "typescript": "~5.3.3", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "wonka": "^6.3.4" }, "devDependencies": { "@babel/core": "^7.25.2", diff --git a/frontend/src/components/Tag.tsx b/frontend/src/components/Tag.tsx index fb2cf91..0b60554 100644 --- a/frontend/src/components/Tag.tsx +++ b/frontend/src/components/Tag.tsx @@ -73,6 +73,7 @@ export function Tag({ {icon} + {children} diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 7ba78c1..9eb1ccd 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,4 +1,4 @@ -export const API_URL = "https://44c7-155-33-132-43.ngrok-free.app"; -export const SUPABASE_URL = "https://145e-155-33-132-43.ngrok-free.app"; +export const API_URL = "https://cbdc-155-33-135-54.ngrok-free.app"; +export const SUPABASE_URL = "https://d9c1-155-33-135-54.ngrok-free.app"; export const SUPABASE_JWT_SECRET = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"; diff --git a/frontend/src/screens/portfolio/PortfolioScreen.tsx b/frontend/src/screens/portfolio/PortfolioScreen.tsx index 9078705..f5c02f8 100644 --- a/frontend/src/screens/portfolio/PortfolioScreen.tsx +++ b/frontend/src/screens/portfolio/PortfolioScreen.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +//Change loading page and if a portfolio cant be loaded page +import React, { useEffect, useState } from 'react'; import { Image, Text, View, TouchableOpacity } from 'react-native'; import { NavigationScreenProp } from 'react-navigation'; import { styled } from 'nativewind'; @@ -6,6 +7,10 @@ import { ScrollView } from 'react-native'; import PortfolioItem from './components/PortfolioItem'; import PortfolioDetails from './components/PortfolioDetails'; import UpdateCard from './components/PortfolioUpdateCard'; +import { useProjectTotalFunded, useAllProjects, useProjectPosts } from "../../services/project"; +import {useInvestorHistory, useInvestorPortfolio} from "../../services/investor"; + + interface PortfolioScreenProps { // This actually should be `any`, so disabling the linter rule @@ -17,11 +22,106 @@ const StyledView = styled(View); const StyledText = styled(Text); const StyledScrollView = styled(ScrollView); const StyledImage = styled(Image); -const StyledTouchableOpacity = styled(TouchableOpacity); +const StyledTouchableOpacity= styled(TouchableOpacity); + +function calculateDaysRemaining(completionDateStr) { + const today = new Date(); + const [monthStr, yearStr] = completionDateStr.split(" "); + const completionDate = new Date(`${monthStr} 1, ${yearStr}`); + + const timeDifference = completionDate.getTime() - today.getTime(); + + return Math.ceil(timeDifference / (1000 * 60 * 60 * 24)); +} + +function calculateExpMaturity(projects) { + let totalDays = 0; + let validProjects = 0; + + projects.forEach(project => { + const daysRemaining = calculateDaysRemaining(project.completion_date); + + if (daysRemaining !== null) { + totalDays += daysRemaining; + validProjects++; + } + }); + + if (validProjects === 0) { + console.error("No valid projects to calculate average maturity."); + return null; + } + + return totalDays / validProjects; +} + +function netPortfolioValue(projects) { + let value = 0; + + projects.forEach(project => { + value = value + project.funding_goal_cents + }); + + return value / 100; +} + +function calculateMarketValue(investments) { + let marketValue = 0; + + investments.forEach(investment => { + marketValue = marketValue + investment.fundedCents + }); + + return marketValue/100; +} + +const [totalInvested, setTotalInvested] = useState(0); + const { portfolio } = useInvestorPortfolio(); + + useEffect(() => { + if (portfolio) { + const total = Object.values(portfolio).reduce((sum, value) => sum + value, 0); + setTotalInvested(total); + } + }, [portfolio]); + export default function PorfolioScreen({ navigation }: PortfolioScreenProps) { const [activeTab, setActiveTab] = useState('Your Projects'); + const { portfolio, isLoading: portfolioLoading } = useInvestorPortfolio(); + const { allProjects, isLoading: projectsLoading } = useAllProjects(); + const {history, isLoading: historyLoading } = useInvestorHistory(1, 1000); //dont know what to put for the pages - maybe change later? + + console.log(portfolio); + + if (portfolioLoading || projectsLoading || historyLoading) { + return ( + + + Loading + + ); + } + + if (!portfolio) { + return ( + + + Failed to load portfolio. Please try again later. + + ) + } + + if (!allProjects || allProjects.length === 0) { + return ( + + + No projects. + + ) + } + return ( {/* Padding */} @@ -30,139 +130,90 @@ export default function PorfolioScreen({ navigation }: PortfolioScreenProps) { - Your Portfolio + Your Portfolio - {/* */} + > + + - + {/* Tab section */} - - - setActiveTab('Your Projects')} - className={`items-center py-2 rounded-t-lg ${activeTab === 'Your Projects' ? 'bg-green-900' : 'bg-white'}`} - > - - Your Projects - - - - - setActiveTab('Updates')} - className={` items-center py-2 rounded-t-lg ${activeTab === 'Updates' ? 'bg-green-900' : 'bg-white'}`} - > - - Updates - - - - - - - {/*Your Projects*/} - {activeTab === 'Your Projects' ? ( - - - {' '} - 8 Total Investments - - - - - - - - - + + + setActiveTab('Your Projects')} className={`items-center h-14 py-2 rounded-tr-[27px] rounded-tl-[27px] ${activeTab === 'Your Projects' ? 'bg-white' : 'bg-defaultPrimary'}`}> + + Your Projects + + - ) : ( - - {/* Updates */} - + ) : } */} + + setActiveTab('Updates')} className={`items-center h-14 py-2 rounded-tr-[27px] ${activeTab === 'Updates' ? 'bg-white' : 'bg-defaultPrimary'}`}> + + Updates + + - )} + + + {/*Your Projects*/} + {activeTab === 'Your Projects' ? ( + + + + {allProjects.length} Total Investments + + {allProjects.map((project) => ( + + } + status={project.milestone} + description={project.description} + initialValue={useProjectTotalFunded(project.id).projectTotalFunded} + finalValue={project.funding_goal_cents/ 100}> + ))} + + + + ) + : + ( + + {/* Updates */} + {allProjects.flatMap(project => { + const { projectPosts = [] } = useProjectPosts(project.id) ?? {}; + return projectPosts.map((post) => ( + + + + )); + })} + + + )} ); -} +}; diff --git a/frontend/src/screens/portfolio/components/PortfolioDetails.tsx b/frontend/src/screens/portfolio/components/PortfolioDetails.tsx index f32eb33..2abef81 100644 --- a/frontend/src/screens/portfolio/components/PortfolioDetails.tsx +++ b/frontend/src/screens/portfolio/components/PortfolioDetails.tsx @@ -1,65 +1,87 @@ -import React from 'react'; -import { View, Text, Image } from 'react-native'; +import React, { useState } from 'react'; +import { View, Text, Image, TouchableOpacity } from 'react-native'; import { styled } from 'nativewind'; const StyledView = styled(View); const StyledText = styled(Text); const StyledImage = styled(Image); +const StyledTouchableOpacity = styled(TouchableOpacity); -const PortfolioDetails = () => { - return ( - - {/* Top Part */} - - - Net Portfolio Value - - - - - $12,345.67 - - $350.23 - + 9.70% - Total Return - - - {/* Four grid */} - - - - - Market Value - $10,000.00 - - - - - Cash Value - $2,345.67 - - - - - - - Total Positions - 11 +interface PortfolioDetailsProps { + netPortfolioValue: number; + portfolioChangeAmount: number; + marketValue: number; + cashValue: number; + totalProjects: number; + expMaturity: number; + +} + +const PortfolioDetails = ({netPortfolioValue, portfolioChangeAmount, marketValue, cashValue, totalProjects, expMaturity} : PortfolioDetailsProps & { className?: string })=> { + + const [hideValues, setHideValues] = useState(false); + + + const displayValue = (value) => { + return hideValues ? '****' : value; + }; + + const [isEyeOpen, setIsEyeOpen] = useState(true); + + const toggleHideValues = () => { + setHideValues(!hideValues); + setIsEyeOpen(!isEyeOpen); + }; + + + return ( + + {/* Top Part */} + + + Net Portfolio Value + + + + + {netPortfolioValue} + + = 0 ? 'color-success' : 'color-error'} font-sourceSans3Bold text-[16px]`}> {portfolioChangeAmount} + = 0 ? 'color-success' : 'color-error'} px-4 font-sourceSans3Bold text-[16px]`}>{displayValue((portfolioChangeAmount/netPortfolioValue*100).toFixed(2) + '%')} + Total Return + - - - - Exp Maturity - 12.5 Years + {/* Four grid */} + + + + Market Value + {marketValue} + + + Cash Value + {cashValue} + + + + + Total Projects + {totalProjects} + + + Exp Maturity + {expMaturity} + + - - - - ); + + ); }; export default PortfolioDetails; diff --git a/frontend/src/screens/portfolio/components/PortfolioItem.tsx b/frontend/src/screens/portfolio/components/PortfolioItem.tsx index 88c5c50..2c79fbc 100644 --- a/frontend/src/screens/portfolio/components/PortfolioItem.tsx +++ b/frontend/src/screens/portfolio/components/PortfolioItem.tsx @@ -1,93 +1,116 @@ import React from 'react'; -import { View, Text, Image, ImageBackground } from 'react-native'; +import { View, Text, Image } from 'react-native'; import { styled } from 'nativewind'; +import Card from "../../../components/Card" +import Tag from "../../../components/Tag" + + const StyledView = styled(View); const StyledText = styled(Text); +const StyledImage = styled(Image); interface PortfolioItemProps { - address: string; - location: string; - price: number; - duration: string; - invested: number; - completion: number; - imageUrl: string; + address: string; + location: string; + description: string; + initialValue: number; + finalValue: number; + image: React.ReactNode; + status: string; } -const PortfolioItem = ({ - address, - location, - price, - duration, - invested, - completion, - imageUrl, -}: PortfolioItemProps) => { - return ( - - - {/* Left Side of the Card*/} - - - 333 Market Street - - San Francisco, CA - - - - Commercial Development - - - - - Land Control Secured - - - - - - Value - - - $250.00 - - - - - Exp Return - - 5.12% - +const PortfolioItem = ({address, location, description, initialValue, finalValue, image, status} : PortfolioItemProps & { className?: string })=> { + return ( + + + {/* Left Side of the Card*/} + + + {address} + {location} + + {description} + + + {status == "Sold" && + + Sold} icon = {} level = {'success'} > + + } + {status == "Construction Underway" && + + Construction Underway} icon = {} level = {'successSubdued'} > + + } + {status == "Construction Complete" && + + Construction Complete} icon = {} level = {'successSubdued'} className = '' > + + } + {status == "Operational" && + + Operational} icon = {} level = {'successSubdued'} > + + } + {status == "Construction Started" && + + Construction Started} icon = {} level = {'neutralSubdued'} > + + } + {status == "Permitting Secured" && + + Permitting Secured} icon = {} level = {'neutralSubdued'} > + + } + {status == "Design Complete" && + + Design Complete} icon = {} level = {'neutralSubdued'} > + + } + + + + + Final Value + {finalValue} + + + Initial Value + {initialValue} + + + + + + {/* Right Side of the Card*/} + + + + + {/* + */} + + {image} + + + Total Returns + {((finalValue - initialValue)/initialValue) * 100} + + - - - {/* Right Side of the Card*/} - - - - - - - Time Line - - 6 Years - - - - - ); + + ); }; export default PortfolioItem; diff --git a/frontend/src/screens/portfolio/components/PortfolioUpdateCard.tsx b/frontend/src/screens/portfolio/components/PortfolioUpdateCard.tsx index 44f5deb..af79ad9 100644 --- a/frontend/src/screens/portfolio/components/PortfolioUpdateCard.tsx +++ b/frontend/src/screens/portfolio/components/PortfolioUpdateCard.tsx @@ -1,43 +1,86 @@ import React from 'react'; -import { View, Text, ImageBackground } from 'react-native'; +import { View, Text, ImageBackground, Image} from 'react-native'; import { styled } from 'nativewind'; -import UpdateText from './PortfolioUpdateText'; import SideBySide from '../../../components/SideBySide'; +import Tag from "../../../components/Tag" + const StyledView = styled(View); const StyledText = styled(Text); interface UpdateCardProps { - topText: string; - bottomText: string; - quantity: string; + topText: string; + bottomText: string; + status: string; } -const UpdateCard = ({ topText, bottomText, quantity }: UpdateCardProps) => { - return ( - - - { + return ( + + + + + + {topText} + {status == "Sold" && + + Sold} icon = {} level = {'success'} > + + } + {status == "Construction Underway" && + + Construction Underway} icon = {} level = {'successSubdued'} > + + } + {status == "Construction Complete" && + + Construction Complete} icon = {} level = {'successSubdued'} className = '' > + + } + {status == "Operational" && + + Operational} icon = {} level = {'successSubdued'} > + + } + {status == "Construction Started" && + + Construction Started} icon = {} level = {'neutralSubdued'} > + + } + {status == "Permitting Secured" && + + Permitting Secured} icon = {} level = {'neutralSubdued'} > + + } + {status == "Design Complete" && + + Design Complete} icon = {} level = {'neutralSubdued'} > + + } + {bottomText} + + + + } + rightComponent={''} + spacing="mr-4" + containerStyle="flex-1" /> - } - rightComponent={{quantity}} - spacing='mr-4' - containerStyle='flex-1' - /> - - ); -}; - + ); + }; + export default UpdateCard; diff --git a/frontend/src/screens/portfolio/components/PortfolioUpdateText.tsx b/frontend/src/screens/portfolio/components/PortfolioUpdateText.tsx deleted file mode 100644 index 692212f..0000000 --- a/frontend/src/screens/portfolio/components/PortfolioUpdateText.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { View, Text } from 'react-native'; -import { styled } from 'nativewind'; - -const StyledView = styled(View); -const StyledText = styled(Text); - -interface UpdateTextProps { - topText: string; - bottomText: string; -} - -const UpdateText = ({ topText, bottomText }: UpdateTextProps) => { - return ( - - {topText} - {bottomText} - - ); -}; - -export default UpdateText; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f3b618b..e490f0f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -9457,7 +9457,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wonka@^6.3.2: +wonka@^6.3.2, wonka@^6.3.4: version "6.3.4" resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594" integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==