Skip to content

Commit

Permalink
Projectscreen (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-brennan2005 authored Dec 4, 2024
1 parent 0d7cd5b commit 6ab1297
Show file tree
Hide file tree
Showing 30 changed files with 549 additions and 10,189 deletions.
13 changes: 0 additions & 13 deletions backend/internal/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package auth
import (
"backend/internal/api_errors"
"backend/internal/config"
"backend/internal/transactions"

"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v5"
Expand Down Expand Up @@ -36,18 +35,6 @@ func (a *AuthFactory) Middleware() func(ctx *fiber.Ctx) error {
return err
}

investorExists, err := transactions.CheckInvestorExists(a.DB, subject)
if err != nil {
return err
}

if !investorExists {
err := transactions.CreateInvestor(a.DB, subject)
if err != nil {
return err
}
}

ctx.Locals("userId", subject)
return ctx.Next()
}
Expand Down
40 changes: 40 additions & 0 deletions backend/internal/controllers/investors.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ func (c *InvestorsController) GetProfile(ctx *fiber.Ctx) error {
return ctx.Status(fiber.StatusOK).JSON(investorProfile)
}

func (c *InvestorsController) CreateProfile(ctx *fiber.Ctx) error {
userId, ok := ctx.Locals("userId").(string)
if !ok {
return &api_errors.INVALID_UUID
}

id, err := uuid.Parse(userId)
if err != nil {
return &api_errors.INVALID_UUID
}

var body models.InvestorProfile
if err := ctx.BodyParser(&body); err != nil {
return &api_errors.INVALID_REQUEST_BODY
}

err = transactions.CreateInvestor(c.ServiceParams.DB, id.String(), body)
if err != nil {
return err
}

return ctx.SendStatus(fiber.StatusOK)
}

func (c *InvestorsController) UpdateProfile(ctx *fiber.Ctx) error {
userId, ok := ctx.Locals("userId").(string)
if !ok {
Expand Down Expand Up @@ -150,3 +174,19 @@ func (c *InvestorsController) GetInvestor(ctx *fiber.Ctx) error {

return ctx.Status(fiber.StatusOK).JSON(investor)
}

func (c *InvestorsController) GetCashBalance(ctx *fiber.Ctx) error {
userId, ok := ctx.Locals("userId").(string)
if !ok {
return &api_errors.INVALID_UUID
}

balance, err := transactions.GetCashBalance(c.ServiceParams.DB, userId)
if err != nil {
return err
}

return ctx.Status(fiber.StatusOK).JSON(fiber.Map{
"cash_balance_cents": balance,
})
}
15 changes: 11 additions & 4 deletions backend/internal/controllers/plaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,22 @@ func (c *PlaidController) Invest(ctx *fiber.Ctx) error {
return err
}

// Record the investment
err = transactions.RecordInvestment(c.ServiceParams.DB, id, body.PropertyID, body.Amount, "")
transactionId, err := transactions.RecordInvestment(c.ServiceParams.DB, id, body.PropertyID, body.Amount)
if err != nil {
return err
}

type ResponseBody struct {
TransactionId uuid.UUID `json:"transaction_id"`
NominalCents int `json:"nominal_cents"`
AdministrativeCents int `json:"administrative_cents"`
}

// Return success response
return ctx.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Investment successful",
return ctx.Status(fiber.StatusOK).JSON(ResponseBody{
TransactionId: transactionId,
NominalCents: amountCents,
AdministrativeCents: 0,
})
}

Expand Down
2 changes: 2 additions & 0 deletions backend/internal/routes/investors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ func Investors(params types.RouterParams) {
// api/v1/investors/*
investors := params.Router.Group("/investors")
investors.Get("/profile", params.Auth.Middleware(), investorsController.GetProfile)
investors.Post("/profile", params.Auth.Middleware(), investorsController.CreateProfile)
investors.Put("/profile", params.Auth.Middleware(), investorsController.UpdateProfile)
investors.Get("/portfolio", params.Auth.Middleware(), investorsController.GetPortfolio)
investors.Get("/history", params.Auth.Middleware(), investorsController.GetHistory)
investors.Get("/", params.Auth.Middleware(), investorsController.GetInvestor)
investors.Get("/balance", params.Auth.Middleware(), investorsController.GetCashBalance)
}
22 changes: 11 additions & 11 deletions backend/internal/transactions/investors.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func CheckInvestorExists(pool *pgxpool.Pool, investorID string) (bool, error) {
return true, nil
}

func CreateInvestor(pool *pgxpool.Pool, supabaseID string) error {
func CreateInvestor(pool *pgxpool.Pool, supabaseID string, profile models.InvestorProfile) error {
query := `
INSERT INTO investors (
supabase_id,
Expand All @@ -49,16 +49,16 @@ func CreateInvestor(pool *pgxpool.Pool, supabaseID string) error {
context.Background(),
query,
supabaseID,
"John",
"Doe",
"[email protected]",
"000-000-0000",
"000-00-0000",
"123",
"Main St",
"Anytown",
"MA",
"12345",
profile.FirstName,
profile.LastName,
profile.Email,
profile.PhoneNumber,
profile.SSN,
profile.Premise,
profile.Street,
profile.Locality,
profile.State,
profile.Zipcode,
)
if err != nil {
return fmt.Errorf("failed to insert investor: %w", err)
Expand Down
19 changes: 13 additions & 6 deletions backend/internal/transactions/plaid.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,24 @@ func GetFirstAccountID(plaidClient *plaid.APIClient, accessToken string) (string
return accounts[0].GetAccountId(), nil
}

func RecordInvestment(db *pgxpool.Pool, investorID uuid.UUID, propertyID, amount, transferID string) error {
func RecordInvestment(db *pgxpool.Pool, investorID uuid.UUID, propertyID, amount string) (uuid.UUID, error) {
query := `
INSERT INTO investor_investments (investor_id, project_id, funded_cents, transfer_id)
VALUES ($1, $2, $3, $4)
INSERT INTO investor_investments (investor_id, project_id, funded_cents)
VALUES ($1, $2, $3)
RETURNING id
`
amountCents, err := strconv.Atoi(amount)
if err != nil {
return err
return uuid.Nil, err
}
_, err = db.Exec(context.Background(), query, investorID, propertyID, amountCents, transferID)
return err

var id uuid.UUID
err = db.QueryRow(context.Background(), query, investorID, propertyID, amountCents).Scan(&id)
if err != nil {
return uuid.Nil, err
}

return id, nil
}

func UpdateCashBalance(db *pgxpool.Pool, investorID uuid.UUID, amount string, operation string) error {
Expand Down
3 changes: 2 additions & 1 deletion frontend/App.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useEffect } from 'react';
import { View } from 'react-native';
import 'react-native-get-random-values';
import { NavigationContainer } from '@react-navigation/native';
import { useFonts } from 'expo-font';
Expand Down Expand Up @@ -38,7 +39,7 @@ export default function App() {
<AuthProvider>
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<RootNavigator />
<RootNavigator />
</NavigationContainer>
</QueryClientProvider>
</AuthProvider>
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"expo": "52",
"expo-constants": "^17.0.3",
"expo-font": "~13.0.1",
"expo-linear-gradient": "~14.0.1",
"expo-splash-screen": "~0.29.8",
"expo-status-bar": "~2.0.0",
"nativewind": "^2.0.11",
Expand Down
50 changes: 25 additions & 25 deletions frontend/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React from 'react';
import { View, ViewProps } from 'react-native';
import { styled } from 'nativewind';
import { cva, type VariantProps } from 'class-variance-authority';

const StyledView = styled(View);

const cardVariants = cva('bg-white rounded-[16px] border border-border p-4', {
variants: {},
defaultVariants: {},
});

interface CardProps extends ViewProps, VariantProps<typeof cardVariants> {
children: React.ReactNode;
}

export function Card({ children, className, ...props }: CardProps & { className?: string }) {
return (
<StyledView className={`${cardVariants()} ${className || ''}`} {...props}>
{children}
</StyledView>
);
}

export default Card;
import React from 'react';
import { View, ViewProps } from 'react-native';
import { styled } from 'nativewind';
import { cva, type VariantProps } from 'class-variance-authority';

const StyledView = styled(View);

const cardVariants = cva('bg-white rounded-[16px] border border-[#DDDDDD] p-4', {
variants: {},
defaultVariants: {},
});

interface CardProps extends ViewProps, VariantProps<typeof cardVariants> {
children: React.ReactNode;
}

export function Card({ children, className, ...props }: CardProps & { className?: string }) {
return (
<StyledView className={`${cardVariants()} ${className || ''}`} {...props}>
{children}
</StyledView>
);
}

export default Card;
4 changes: 2 additions & 2 deletions frontend/src/components/Divider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { View, Image, ImageSourcePropType } from 'react-native';
import { styled } from 'nativewind';
import { CaptionText } from './typography';
import { CaptionText } from './Typography';

const StyledView = styled(View);
const StyledImage = styled(Image);
Expand All @@ -22,7 +22,7 @@ const Divider: React.FC<DividerProps> = ({ text, image }) => {
<StyledView className='flex-1 h-[0.2vh] bg-gray-200' />
{text && (
//Allows for conditional rendering of text element
//Text element styling is taken from typography.tsx
//Text element styling is taken from Typography.tsx
<StyledView className='items-center px-[0.5vh]'>
<CaptionText className='text-gray-300'>{text}</CaptionText>
</StyledView>
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const API_URL = 'https://e966-155-33-133-58.ngrok-free.app';
export const SUPABASE_URL = 'https://c462-155-33-133-58.ngrok-free.app';
export const SUPABASE_JWT_SECRET =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0';
export const API_URL = "https://6ed0-155-33-133-6.ngrok-free.app";
export const SUPABASE_URL = "https://0427-155-33-133-6.ngrok-free.app";
export const SUPABASE_JWT_SECRET = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";

55 changes: 50 additions & 5 deletions frontend/src/navigation/ProjectNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import React from 'react';
import { SafeAreaView, Text, TouchableOpacity, View } from 'react-native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import ProjectScreen from '../screens/project/ProjectScreen';
import ProjectInvestScreen from '../screens/project/ProjectInvestScreen';
import ProjectUpdatesScreen from '../screens/project/ProjectUpdatesScreen';
import ProjectImagesScreen from '../screens/project/ProjectImagesScreen';
import ProjectMapScreen from '../screens/project/ProjectMapScreen';
import { styled } from 'nativewind';
import ProjectInvestSuccessScreen from '../screens/project/ProjectInvestSuccessScreen';

const Stack = createNativeStackNavigator();

const StyledText = styled(Text);
const StyledView = styled(View);
const StyledTouchableOpacity = styled(TouchableOpacity);
const StyledSafeAreaView = styled(SafeAreaView);

export default function ProjectNavigator() {
return (
<Stack.Navigator initialRouteName='project'>
<Stack.Navigator initialRouteName='project' >
<Stack.Screen
name='project'
component={ProjectScreen}
options={{ title: 'Project details', headerShown: true }}
initialParams={{projectId: 'c3733692-5a86-441f-8ad0-9c32c648bb72'}}
options={{ title: 'Project details', header: (props) => {
return (
<StyledSafeAreaView className="bg-surfaceBG">
<StyledText className="font-heading text-2xl text-center p-4">Project details</StyledText>
</StyledSafeAreaView>
)
} }}
/>
<Stack.Screen
name='project-invest'
component={ProjectInvestScreen}
options={{ title: 'Invest', headerShown: true }}
options={{ title: 'Invest', header: (props) => {
const canGoBack = props.navigation.canGoBack()
return (
<StyledSafeAreaView className="bg-surfaceBG">
<StyledText className="font-heading text-2xl text-center p-4">Invest</StyledText>
</StyledSafeAreaView>
)
}}}
/>
<Stack.Screen
name='project-updates'
Expand All @@ -30,12 +52,35 @@ export default function ProjectNavigator() {
<Stack.Screen
name='project-images'
component={ProjectImagesScreen}
options={{ title: 'Project images', headerShown: true }}
options={{ title: 'Project images', header: (props) => {
return (
<StyledSafeAreaView className="bg-surfaceBG">
<StyledText className="font-heading text-2xl text-center p-4">Project images</StyledText>
</StyledSafeAreaView>
)
} }}
/>
<Stack.Screen
name='project-map'
component={ProjectMapScreen}
options={{ title: 'Project location', headerShown: true }}
options={{ title: 'Project map', header: (props) => {
return (
<StyledSafeAreaView className="bg-surfaceBG">
<StyledText className="font-heading text-2xl text-center p-4">Project map</StyledText>
</StyledSafeAreaView>
)
} }}
/>
<Stack.Screen
name='project-invest-success'
component={ProjectInvestSuccessScreen}
options={{ title: 'Deposit', header: (props) => {
return (
<StyledSafeAreaView className="bg-surfaceBG">
<StyledText className="font-heading text-2xl text-center p-4">Deposit</StyledText>
</StyledSafeAreaView>
)
} }}
/>
</Stack.Navigator>
);
Expand Down
11 changes: 2 additions & 9 deletions frontend/src/navigation/RootNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,8 @@ import LoginNavigator from "./LoginNavigator";

// Navigates user to the log in screen if seesion is not found (i.e. user not logged in)
export default function RootNavigator() {
console.log('RootNavigator rendering');

const { session, isLoading, isInSignupFlow } = useAuth();
console.log('RootNavigator values:', {
session: session,
isLoading: isLoading,
isInSignupFlow: isInSignupFlow
});

const { session, isLoading } = useAuth();

if (isLoading) {
return null; // or some loading screen (maybe we make in future?)
}
Expand Down
Loading

0 comments on commit 6ab1297

Please sign in to comment.