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==