diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 34ac51f..a5c6652 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -29,7 +29,7 @@ export async function login(_previousState: string, formData: FormData): Promise console.log(`User ${username} logged in successfully`); // Redirect to dashboard after successful login - redirect('/dashboard'); + redirect('/global-assets/lookup'); } catch (error) { // Type narrowing for the axios error if (axios.isAxiosError(error)) { diff --git a/src/app/(dashboard)/_components/BalanceDisplay.tsx b/src/app/(dashboard)/_components/BalanceDisplay.tsx new file mode 100644 index 0000000..fb5c955 --- /dev/null +++ b/src/app/(dashboard)/_components/BalanceDisplay.tsx @@ -0,0 +1,24 @@ +// app/(dashboard)/_components/BalanceDisplay.tsx +'use client'; + +import {useBalance} from '@/context/BalanceContext'; +import {CircleDollarSign} from 'lucide-react'; + +export default function BalanceDisplay() { + const {balance, isLoading} = useBalance(); + + return ( +
+ + {isLoading ? ( + + Loading... + + ) : ( + + ${balance?.toFixed(2)} + + )} +
+ ); +} diff --git a/src/app/(dashboard)/_components/HeaderSearchBar.tsx b/src/app/(dashboard)/_components/HeaderSearchBar.tsx new file mode 100644 index 0000000..8286385 --- /dev/null +++ b/src/app/(dashboard)/_components/HeaderSearchBar.tsx @@ -0,0 +1,253 @@ +'use client' + +import {useRouter} from "next/navigation"; +import {Button} from "@/components/ui/button"; +import {ScrollArea} from "@/components/ui/scroll-area"; +import {useEffect, useState} from "react"; +import {useDebounce} from "use-debounce"; +import {SearchResult} from "../global-assets/lookup/_utils/definitions"; +import {searchYahooFinance} from "@/app/(dashboard)/global-assets/lookup/_utils/actions"; +import {Popover} from "@radix-ui/react-popover"; +import {PopoverContent, PopoverTrigger} from "@/components/ui/popover"; +import {Input} from "@/components/ui/input"; +import {Loader2, Search, X} from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import {Badge} from "@/components/ui/badge"; + +function HeaderSearchBar({className = ''}: { className?: string }) { + const [searchQuery, setSearchQuery] = useState(''); + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [searchResults, setSearchResults] = useState(null); + const [error, setError] = useState(null); + const router = useRouter(); + + // Get the first value from the array returned by useDebounce + const [debouncedQuery] = useDebounce(searchQuery, 1000); + + // Function to fetch search results + const fetchSearchResults = async (query: string) => { + if (query.length < 2) { + setIsOpen(false); + return; + } + + setIsLoading(true); + setError(null); + + try { + const response = await searchYahooFinance({ + query, + newsCount: 3, + quoteCount: 4 + }); + + if (response.success) { + setSearchResults(response.data); + setIsOpen(true); + } else { + setError(response.error); + setSearchResults(null); + } + } catch (err) { + console.error(err); + setError("An unexpected error occurred"); + setSearchResults(null); + } finally { + setIsLoading(false); + } + }; + + // Handle debounced query changes + useEffect(() => { + if (debouncedQuery.length >= 2) { + fetchSearchResults(debouncedQuery).then(); + } else { + setIsOpen(false); + } + }, [debouncedQuery]); + + // Handle view all results click + const handleViewAllResults = () => { + if (searchQuery) { + router.push(`/global-assets/lookup?query=${encodeURIComponent(searchQuery)}`); + setIsOpen(false); + } + }; + + const handleClearSearch = () => { + setSearchQuery(""); + setIsOpen(false); + } + + return ( +
+ + +
+ setSearchQuery(e.target.value)} + placeholder="Search stocks..." + className="w-full h-9 pl-9 pr-8 text-sm focus-visible:ring-1" + /> + {isLoading ? ( + + ) : ( + + )} + {searchQuery && ( + + )} +
+
+ e.preventDefault()} + > + + + + +
+
+ ); +} + +function HeaderSearchResults({ + data, + error, + onViewAllResults + }: { + data: SearchResult | null; + error: string | null; + onViewAllResults: () => void; +}) { + if (error) { + return ( +
+ Error fetching results: {error} +
+ ); + } + + const {quotes = [], news = []} = data || {}; + const hasResults = quotes.length > 0 || news.length > 0; + + if (!hasResults) { + return ( +
+ No results found. Try a different search term. +
+ ); + } + + return ( +
+ {/* Quotes Section */} + {quotes.length > 0 && ( +
+

Stocks & Companies

+
+ {quotes.map((quote) => ( + +
+ {quote.symbol} + {quote.shortName || 'N/A'} +
+
+ {quote.exchange || 'N/A'} +
+ + ))} +
+
+ )} + + {/* News Section */} + {news.length > 0 && ( +
+

Latest News

+
+ {news.map((item) => ( + +
+ {item.thumbnail && ( +
+ {item.title} +
+ )} +
+

{item.title}

+
+ {item.publisher} + {item.relatedTickers && item.relatedTickers.length > 0 && ( +
+ {item.relatedTickers.slice(0, 1).map((ticker) => ( + + {ticker} + + ))} + {item.relatedTickers.length > 1 && ( + + +{item.relatedTickers.length - 1} + + )} +
+ )} +
+
+
+ + ))} +
+
+ )} + + {/* View all results link */} +
+ +
+
+ ); +} + +export default HeaderSearchBar; \ No newline at end of file diff --git a/src/app/(dashboard)/_components/LoadingAnimation.tsx b/src/app/(dashboard)/_components/LoadingAnimation.tsx new file mode 100644 index 0000000..0959e5f --- /dev/null +++ b/src/app/(dashboard)/_components/LoadingAnimation.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +const LoadingAnimation = () => { + return ( +
+
+
+
+
+
+
+
+ ); +}; + +export default LoadingAnimation; \ No newline at end of file diff --git a/src/app/(dashboard)/_components/app-sidebar-links.tsx b/src/app/(dashboard)/_components/app-sidebar-links.tsx index 3895952..d6f37fc 100644 --- a/src/app/(dashboard)/_components/app-sidebar-links.tsx +++ b/src/app/(dashboard)/_components/app-sidebar-links.tsx @@ -8,8 +8,6 @@ import {SidebarData} from "@/app/(dashboard)/_utils/types"; import { ChartCandlestickIcon, DollarSign, Earth, - FolderGit2, - LayoutGrid, PieChart, Users, ChartColumnIncreasing @@ -17,11 +15,6 @@ import { const userSidebar: SidebarData = { navMain: [ - { - title: "Dashboard", - url: "/dashboard", - icon: LayoutGrid, - }, { title: "Global Market", url: '/assets', @@ -62,28 +55,11 @@ const userSidebar: SidebarData = { title: "StockMarket Prediction", url: "/dashboard/stockmarketprediction", icon: ChartColumnIncreasing - }], - guides: [ - { - name: "ML Model Notebook", - url: "/notebooks", - icon: FolderGit2, - }, - { - name: "Documentation", - url: "/docs", - icon: Users, - }, - ], + }] } const adminSidebar: SidebarData = { navMain: [ - { - title: "Dashboard", - url: "/dashboard", - icon: LayoutGrid, - }, { title: "Global Assets", url: '/assets', @@ -109,14 +85,24 @@ const adminSidebar: SidebarData = { title: "System Assets", url: '/assets/db', icon: ChartCandlestickIcon, + }, + { + title: "Budget Tracker", + url: "/dashboard/budget", + icon: DollarSign, + }, + { + title: "Portfolio Optimization", + url: "/dashboard/portfolio", + icon: PieChart + }, + { + title: "StockMarket Prediction", + url: "/dashboard/stockmarketprediction", + icon: ChartColumnIncreasing } ], guides: [ - { - name: "ML Model Notebook", - url: "/notebooks", - icon: FolderGit2, - }, { name: "Documentation", url: "/docs", @@ -130,7 +116,8 @@ const AppSidebarLinks = ({role}: { role: Role }) => { return ( <> - + {sidebar.guides ? + : null} ); }; diff --git a/src/app/(dashboard)/_utils/types.ts b/src/app/(dashboard)/_utils/types.ts index ddc0d0a..496f89f 100644 --- a/src/app/(dashboard)/_utils/types.ts +++ b/src/app/(dashboard)/_utils/types.ts @@ -2,30 +2,30 @@ import {LucideIcon} from "lucide-react" // Type for navigation sub-items export type NavSubItem = { - title: string - url: string + title: string + url: string } // Type for main navigation items export type NavItem = { - title: string - url: string - icon: LucideIcon - items?: NavSubItem[], - initiallyExpanded?: boolean + title: string + url: string + icon: LucideIcon + items?: NavSubItem[], + initiallyExpanded?: boolean } // Type for guide items export type GuideItem = { - name: string - url: string - icon: LucideIcon + name: string + url: string + icon: LucideIcon } // Comprehensive type for the entire data structure export type SidebarData = { - navMain: NavItem[] - guides: GuideItem[] + navMain: NavItem[] + guides?: GuideItem[] } // Usage example: diff --git a/src/app/(dashboard)/dashboard/budget/_components/AddTransactionDialog.tsx b/src/app/(dashboard)/dashboard/budget/_components/AddTransactionDialog.tsx new file mode 100644 index 0000000..32f18d4 --- /dev/null +++ b/src/app/(dashboard)/dashboard/budget/_components/AddTransactionDialog.tsx @@ -0,0 +1,210 @@ +"use client"; + +import React, {useState} from "react"; +import {Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger} from "@/components/ui/dialog"; +import {Button} from "@/components/ui/button"; +import {Input} from "@/components/ui/input"; +import {Label} from "@/components/ui/label"; +// We will remove the Select components as per the request +// import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; +import {createTransaction, categorizeTransaction, TransactionCreate} from "@/lib/budget-lib/budget_api"; +import {useBalance} from "@/context/BalanceContext"; // Assuming BalanceContext.tsx is in the same directory or adjust path +import {toast} from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; // Import default CSS for react-toastify + +interface AddTransactionDialogProps { + userId: string; +} + +// Helper to format date to YYYY-MM-DD for input type="date" +const formatDateForInput = (date: Date): string => { + return date.toISOString().split('T')[0]; +}; + +export function AddTransactionDialog({userId}: AddTransactionDialogProps) { + const [isOpen, setIsOpen] = useState(false); + const {refetch: refetchBalance} = useBalance(); // Get refetch function from context + + const initialTransactionState: Omit & { + category?: string, + created_at?: string + } = { + date: formatDateForInput(new Date()), // Format date for input + reason: "", + amount: 0, + type: "expense" // Default to expense + }; + + const [newTransaction, setNewTransaction] = useState(initialTransactionState); + const [error, setError] = useState(null); + + const handleAddTransaction = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); // Clear previous errors + + if (!newTransaction.reason || newTransaction.amount <= 0 || !newTransaction.date) { + setError("Please fill all required fields and ensure amount is greater than 0."); + toast.error("Please fill all required fields and ensure amount is greater than 0."); + return; + } + + const transactionToCreate: TransactionCreate = { + ...newTransaction, + user_id: userId, + amount: Number(newTransaction.amount), + // API expects date in 'YYYY-MM-DD HH:MM:SS' format or similar, ensure this matches your backend + // Using ISOString and preparing it. Adjust if your backend needs a different exact format. + date: new Date(newTransaction.date).toISOString().replace("T", " ").split(".")[0], + created_at: new Date().toISOString().replace("T", " ").split(".")[0], + type: newTransaction.type as "expense" | "income", + category: "", // Will be set by categorizeTransaction + }; + + const promise = async () => { + // Step 1: Categorize transaction + const categoryReceived = await categorizeTransaction( + transactionToCreate.reason, + transactionToCreate.amount, + transactionToCreate.type + ); + transactionToCreate.category = categoryReceived.category; + + // Step 2: Create transaction + await createTransaction(transactionToCreate); + + // Step 3: Refetch balance + refetchBalance(); // Use await if refetchBalance returns a promise and you need to wait + + // Step 4: Reset form and close dialog on success + setNewTransaction(initialTransactionState); + setIsOpen(false); + }; + + toast.promise( + promise(), + { + pending: 'Adding transaction...', + success: 'Transaction added successfully! 🎉', + error: 'Failed to add transaction. Please try again. 🤯' + } + ).catch(err => { + // The promise itself might reject, or an error within it. + // toast.promise handles errors from the promise, but you can log them too. + console.error("Transaction creation failed:", err); + // setError("Failed to create transaction"); // This could be displayed in the form if needed + }); + }; + + // Handle changes to form inputs + const handleChange = (e: React.ChangeEvent) => { + const {name, value, type} = e.target; + setNewTransaction(prev => ({ + ...prev, + [name]: type === 'number' ? parseFloat(value) : value, + })); + }; + + // Handle type selection (Expense/Income) + const handleTypeSelect = (type: "expense" | "income") => { + setNewTransaction(prev => ({...prev, type})); + }; + + return ( + + + + + + + Add New Transaction + +
+ {/* Transaction Type Tabs */} +
+ +
+ + +
+
+ + {/* Date Input */} +
+ + +
+ + {/* Amount Input */} +
+ + +
+ + {/* Description Input */} +
+ + +
+ + {/* Error Message Display */} + {error &&

{error}

} + + {/* Submit Button */} + +
+
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/budget/page.tsx b/src/app/(dashboard)/dashboard/budget/page.tsx index ef142bd..320169a 100644 --- a/src/app/(dashboard)/dashboard/budget/page.tsx +++ b/src/app/(dashboard)/dashboard/budget/page.tsx @@ -18,6 +18,7 @@ import { calculateSavingsScore, calculateSpendingScore } from "@/app/(dashboard)/dashboard/budget/_utils/utils" +import {AddTransactionDialog} from "@/app/(dashboard)/dashboard/budget/_components/AddTransactionDialog"; export interface TransactionSummary { income: number; @@ -108,36 +109,43 @@ export default function Home() { return (
- - - - Dashboard - - - - Budget Goals - - - - Budget Reports - - - - Predictions - - +
+ + + + Dashboard + + + + Budget Goals + + + + Budget Reports + + + + Predictions + + + +
+ +
+
+ {isLoading ?
diff --git a/src/app/(dashboard)/dashboard/portfolio/_components/port-opt-form.tsx b/src/app/(dashboard)/dashboard/portfolio/_components/port-opt-form.tsx index 1066bdd..75a6a78 100644 --- a/src/app/(dashboard)/dashboard/portfolio/_components/port-opt-form.tsx +++ b/src/app/(dashboard)/dashboard/portfolio/_components/port-opt-form.tsx @@ -67,7 +67,7 @@ interface TickerResponse { tickers: Ticker[]; } -interface budgetData { +export interface budgetData { income: number; expense: number; balance: number; diff --git a/src/app/(dashboard)/dashboard/portfolio/results/page.tsx b/src/app/(dashboard)/dashboard/portfolio/results/page.tsx index baad396..ce8f1d1 100644 --- a/src/app/(dashboard)/dashboard/portfolio/results/page.tsx +++ b/src/app/(dashboard)/dashboard/portfolio/results/page.tsx @@ -1,280 +1,280 @@ "use client"; -import { useSearchParams } from "next/navigation"; -import { motion } from "framer-motion"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { OptimizedPortfolioResult } from "@/lib/types/profile"; +import {useSearchParams} from "next/navigation"; +import {motion} from "framer-motion"; +import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; +import {OptimizedPortfolioResult} from "@/lib/types/profile"; import { - PieChart, - Pie, - Cell, - Tooltip as TooltipComponent, - ResponsiveContainer, + PieChart, + Pie, + Cell, + Tooltip as TooltipComponent, + ResponsiveContainer, } from "recharts"; -import { Badge } from "@/components/ui/badge"; -import { Separator } from "@/components/ui/separator"; +import {Badge} from "@/components/ui/badge"; +import {Separator} from "@/components/ui/separator"; import { - ArrowUpRight, - TrendingUp, - AlertTriangle, - Target, - PieChart as PieChartIcon, + ArrowUpRight, + TrendingUp, + AlertTriangle, + Target, + PieChart as PieChartIcon, } from "lucide-react"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbSeparator, + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, } from "@/components/ui/breadcrumb"; import PortfolioExplanation from "@/components/portfolio/PortfolioExplanation"; export default function PortfolioResultsPage() { - const searchParams = useSearchParams(); - const encodedData = searchParams.get("data"); + const searchParams = useSearchParams(); + const encodedData = searchParams.get("data"); - if (!encodedData) { - return

No result data found.

; - } + if (!encodedData) { + return

No result data found.

; + } - try { - const result = JSON.parse(encodedData) as OptimizedPortfolioResult; + try { + const result = JSON.parse(encodedData) as OptimizedPortfolioResult; - // Prepare data for pie chart - const pieData = Object.entries(result.optimal_weights) - .map(([name, value]) => ({ - name, - value: Number(value) * 100, - })) - .filter((item) => item.value > 0); + // Prepare data for pie chart + const pieData = Object.entries(result.optimal_weights) + .map(([name, value]) => ({ + name, + value: Number(value) * 100, + })) + .filter((item) => item.value > 0); - const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042", "#8884D8"]; + const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042", "#8884D8"]; - const containerVariants = { - hidden: { opacity: 0, y: 20 }, - visible: { opacity: 1, y: 0 }, - }; - console.log(result.method_used); + const containerVariants = { + hidden: {opacity: 0, y: 20}, + visible: {opacity: 1, y: 0}, + }; + console.log(result.method_used); - return ( -
- - - - Dashboard - - - - - Portfolio - - - - - - Results - - - - - -

- Portfolio Optimization Results -

+ return ( +
+ + + + Dashboard + + + + + Portfolio + + + + + + Results + + + + + +

+ Portfolio Optimization Results +

-
- {/* Portfolio Allocation Chart */} - - - - - Portfolio Allocation - - - -
- - - - {pieData.map((entry, index) => ( - - ))} - - - - -
-
- {pieData.map((entry, index) => ( -
-
- - {entry.name}: {entry.value.toFixed(2)}% - -
- ))} -
- - +
+ {/* Portfolio Allocation Chart */} + + + + + Portfolio Allocation + + + +
+ + + + {pieData.map((entry, index) => ( + + ))} + + + + +
+
+ {pieData.map((entry, index) => ( +
+
+ + {entry.name}: {entry.value.toFixed(2)}% + +
+ ))} +
+ + - {/* Key Metrics */} - - - - - Key Metrics - - - -
-
-

- Expected Return -

-
- - {(result.expected_return * 100).toFixed(2)}% - - -
-
-
-

Volatility

-
- - {(result.volatility * 100).toFixed(2)}% - - -
-
-
- -
-

Sharpe Ratio

- - {result.sharpe_ratio.toFixed(2)} - -
-
-

- Investment Goal -

-
- - {result.goal} -
-
-
-

- Optimization Method -

-
- {result.method_used} -
-
-
-
+ {/* Key Metrics */} + + + + + Key Metrics + + + +
+
+

+ Expected Return +

+
+ + {(result.expected_return * 100).toFixed(2)}% + + +
+
+
+

Volatility

+
+ + {(result.volatility * 100).toFixed(2)}% + + +
+
+
+ +
+

Sharpe Ratio

+ + {result.sharpe_ratio.toFixed(2)} + +
+
+

+ Investment Goal +

+
+ + {result.goal} +
+
+
+

+ Optimization Method +

+
+ {result.method_used} +
+
+
+
- {/* Monte Carlo Projection */} - - - Monte Carlo Projection - - -
-
-

- Expected Final Value -

- - $ - {result.monte_carlo_projection.expected_final_value.toLocaleString()} - -
-
-

- Minimum Final Value -

- - $ - {result.monte_carlo_projection.min_final_value.toLocaleString()} - -
-
-

- Maximum Final Value -

- - $ - {result.monte_carlo_projection.max_final_value.toLocaleString()} - -
-
-

- Success Rate -

-
- - {result.monte_carlo_projection.success_rate_percent.toFixed( - 2 - )} - % - - - 80 - ? "success" - : result.monte_carlo_projection - .success_rate_percent > 50 - ? "default" - : "destructive" - } - > - {result.monte_carlo_projection.success_rate_percent > 80 - ? "High" - : result.monte_carlo_projection.success_rate_percent > - 50 - ? "Medium" - : "Low"} - + {/* Monte Carlo Projection */} + + + Monte Carlo Projection + + +
+
+

+ Expected Final Value +

+ + $ + {result.monte_carlo_projection.expected_final_value.toLocaleString()} + +
+
+

+ Minimum Final Value +

+ + $ + {result.monte_carlo_projection.min_final_value.toLocaleString()} + +
+
+

+ Maximum Final Value +

+ + $ + {result.monte_carlo_projection.max_final_value.toLocaleString()} + +
+
+

+ Success Rate +

+
+ + {result.monte_carlo_projection.success_rate_percent.toFixed( + 2 + )} + % + + + 80 + ? "success" + : result.monte_carlo_projection + .success_rate_percent > 50 + ? "default" + : "destructive" + } + > + {result.monte_carlo_projection.success_rate_percent > 80 + ? "High" + : result.monte_carlo_projection.success_rate_percent > + 50 + ? "Medium" + : "Low"} + +
+
+
+
+
-
+ +
+
- - -
- -
- -
-
- ); - } catch (error) { - console.error("Error parsing result:", error); - return

Error parsing result data.

; - } +
+ ); + } catch (error) { + console.error("Error parsing result:", error); + return

Error parsing result data.

; + } } diff --git a/src/app/(dashboard)/docs/page.tsx b/src/app/(dashboard)/docs/page.tsx new file mode 100644 index 0000000..86c904b --- /dev/null +++ b/src/app/(dashboard)/docs/page.tsx @@ -0,0 +1,71 @@ +"use client"; + +import {useEffect} from 'react'; +import {motion} from "framer-motion"; +import {Loader2} from "lucide-react"; // Changed icon to something more docs-related, or keep Loader2 +import {BACKEND_BASE_URL} from "@/lib/const"; + +export default function DocsRedirectPage() { // Renamed component for clarity + const swaggerDocsUrl = `${BACKEND_BASE_URL}/docs`; // Use the constant for the backend URL + + useEffect(() => { + // Wait for a short period (e.g., 1.5 seconds) and then redirect + const timer = setTimeout(() => { + window.location.href = swaggerDocsUrl; + }, 1500); // Adjust delay as needed + + // Cleanup function to clear the timer if the component unmounts + return () => clearTimeout(timer); + }, [swaggerDocsUrl]); // Add swaggerDocsUrl to dependency array if it could change, though it's constant here + + return ( +
+ + + {/* You can use Loader2 or a more specific icon like BookOpenCheck */} + + {/* */} + + + Redirecting to API Docs + + + Please wait while we take you to the Swagger UI for our API. + + + Click here if you are not redirected automatically. + + +
+ ); +} diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 7733b8d..803e983 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,54 +1,62 @@ import {AppSidebar} from "@/app/(dashboard)/_components/app-sidebar" import {Separator} from "@/components/ui/separator" import {SidebarInset, SidebarProvider, SidebarTrigger,} from "@/components/ui/sidebar" -import {Bell, LogOut} from "lucide-react" +import {LogOut} from "lucide-react" import {ModeToggle} from "@/components/ThemeProvider"; -import {Button} from "@/components/ui/button"; import React, {Suspense} from "react"; import {SidebarSkeleton} from "@/app/(dashboard)/_components/sidebar-skeleton"; import Link from "next/link"; +import HeaderSearchBar from "./_components/HeaderSearchBar"; +import {BalanceProvider} from "@/context/BalanceContext"; +import BalanceDisplay from "@/app/(dashboard)/_components/BalanceDisplay"; // Import the new component export default function DashboardLayout({children}: { children: React.ReactNode }) { return ( - - }> - - - -
-
- -
-
- - - - - - - + + + }> + + + +
+
+ +
+ + {/* Search bar in the middle */} +
+ +
+ +
+ + + + + + + +
+
+
+ {children} + {/*
*/} + {/* */} + {/*
*/} + {/* © 2024 - Sem 4 Group J*/} + {/*
*/} + {/*
*/}
-
-
- {children} - {/*
*/} - {/* */} - {/*
*/} - {/* © 2024 - Sem 4 Group J*/} - {/*
*/} - {/*
*/} -
-
-
+ + + ) } \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 8080f5a..dea7c6f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -217,33 +217,33 @@ justify-content: center; align-items: center; gap: 5px; - } - - .typing-dots span { +} + +.typing-dots span { width: 8px; height: 8px; background-color: #4f46e5; /* Customize the color */ border-radius: 50%; animation: typing 1.5s infinite ease-in-out; - } - - .typing-dots span:nth-child(1) { +} + +.typing-dots span:nth-child(1) { animation-delay: 0s; - } - - .typing-dots span:nth-child(2) { +} + +.typing-dots span:nth-child(2) { animation-delay: 0.2s; - } - - .typing-dots span:nth-child(3) { +} + +.typing-dots span:nth-child(3) { animation-delay: 0.4s; - } - - @keyframes typing { +} + +@keyframes typing { 0%, 80%, 100% { - transform: scale(0); + transform: scale(0); } 40% { - transform: scale(1); + transform: scale(1); } - } \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 9da8c59..bd286d9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -95,7 +95,7 @@ export default function Home() { Get Started
diff --git a/src/context/BalanceContext.tsx b/src/context/BalanceContext.tsx new file mode 100644 index 0000000..76c514b --- /dev/null +++ b/src/context/BalanceContext.tsx @@ -0,0 +1,42 @@ +// context/BalanceContext.tsx +'use client'; +import React, {createContext, useContext} from 'react'; +import useSWR from 'swr'; +import AxiosInstance from '@/lib/client-fetcher'; +import {getCurrentUser} from "@/actions/auth"; +import {budgetData} from "@/app/(dashboard)/dashboard/portfolio/_components/port-opt-form"; // your interceptor-based Axios instance + +const fetcher = async () => { + const currentUser = await getCurrentUser() + + const res = await AxiosInstance.get(`/budget/transactions/summary/${currentUser?.user_id}`); + return res.data; +}; + +const BalanceContext = createContext<{ + balance: number | null; + isLoading: boolean; + refetch: () => void; +} | null>(null); + +export const BalanceProvider = ({children}: { children: React.ReactNode }) => { + const {data, error, mutate} = useSWR('/balance', fetcher); + + return ( + mutate(), + }} + > + {children} + + ); +}; + +export const useBalance = () => { + const context = useContext(BalanceContext); + if (!context) throw new Error('useBalance must be used within a BalanceProvider'); + return context; +}; diff --git a/src/middleware.ts b/src/middleware.ts index 5310589..b1c5341 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -82,7 +82,7 @@ export default async function middleware(req: NextRequest) { // Redirect authenticated users away from login page if (session?.sub && (path === "/auth/login" || path === "/auth/register")) { - return NextResponse.redirect(new URL("/dashboard", req.nextUrl)); + return NextResponse.redirect(new URL("/global-assets/lookup", req.nextUrl)); } // Authentication check: Redirect to login if accessing protected route without a session