diff --git a/web/AASTU-web-group-2/a2sv-banking-system/.eslintrc.json b/web/AASTU-web-group-2/a2sv-banking-system/.eslintrc.json new file mode 100644 index 000000000..bffb357a7 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/.gitignore b/web/AASTU-web-group-2/a2sv-banking-system/.gitignore new file mode 100644 index 000000000..fd3dbb571 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/web/AASTU-web-group-2/a2sv-banking-system/README.md b/web/AASTU-web-group-2/a2sv-banking-system/README.md new file mode 100644 index 000000000..c4033664f --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/BarChartForAccounts.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/BarChartForAccounts.tsx new file mode 100644 index 000000000..76f2940fc --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/BarChartForAccounts.tsx @@ -0,0 +1,83 @@ +"use client"; +import { Bar, BarChart, CartesianGrid, Legend, XAxis } from "recharts"; + +import { Card, CardContent } from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +const chartData = [ + { day: "Monday", debit: 186, credit: 80 }, + { day: "Tuesday", debit: 305, credit: 200 }, + { day: "Wednesday", debit: 237, credit: 120 }, + { day: "Thursday", debit: 73, credit: 190 }, + { day: "Friday", debit: 209, credit: 130 }, + { day: "Saturday", debit: 214, credit: 140 }, + { day: "Sunday", debit: 214, credit: 140 }, +]; + +const chartConfig = { + debit: { + label: "Debit", + color: "#1814F3", // Debit color + }, + credit: { + label: "Credit", + color: "#FC7900", // Credit color + }, +} satisfies ChartConfig; + +const CustomLegend = (props: any) => { + const { payload } = props; + + return ( + + ); +}; + +export default function BarChartForAccounts() { + return ( + + + + + } + /> + + value.slice(0, 3)} + /> + } + /> + + + + + + + ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/ListCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/ListCard.tsx new file mode 100644 index 000000000..eb0b1c33c --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/ListCard.tsx @@ -0,0 +1,58 @@ +import React from "react"; +import { IconType } from "react-icons"; +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; // Updated to an array of DataItem objects +}; + +interface Props { + column: Column; + width: string; + darkMode: string +} + +const ListCard = ({ column, width, darkMode }: Props) => { + return ( +
+
+ +
+
+ {column.data.map((item, index) => ( +
+
{item.heading}
+
{item.text}
+
+ ))} +
+
+ ); +}; + +export const ListCardLoading = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default ListCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/Loading.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/Loading.tsx new file mode 100644 index 000000000..603aafb87 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/components/Loading.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import { ListCardLoading } from "./ListCard"; +const Loading = () => { + return ( + <> +
+
+ + + + +
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+
+

+

+
+
+
+ +
+
+

+

+
+
+

+

+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+ + ); +}; + +export default Loading; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/page.tsx new file mode 100644 index 000000000..50506d861 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/accounts/page.tsx @@ -0,0 +1,410 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { + MdHome, + MdSettings, + MdAttachMoney, + MdAccountBalance, +} from "react-icons/md"; +import ListCard from "./components/ListCard"; +import { IconType } from "react-icons"; +import BarChartForAccounts from "./components/BarChartForAccounts"; +import Card from "../components/Page2/Card"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { getCards } from "@/lib/api/cardController"; +import { Card as CardType } from "@/types/cardController.Interface"; +import { getCurrentUser } from "@/lib/api/userControl"; +import { UserInfo } from "@/types/userInterface"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { ListCardLoading } from "./components/ListCard"; +import { + getTransactionIncomes, + getTransactions, + getTransactionsExpenses, +} from "@/lib/api/transactionController"; +import Loading from "./components/Loading"; +import { TransactionData, TransactionResponse } from "@/types/transactionController.interface"; +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; +}; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +const Page = () => { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [getCard, setGetCards] = useState(); + const [currentUser, setCurrentUser] = useState(); + const [balance, setBalance] = useState("Loading..."); + const [income, setIncome] = useState("Loading..."); + const [expense, setExpense] = useState("Loading..."); + const [transaction, setTransaction] = useState([]) + + // Getting the session from the server and Access Token From Refresh + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, [router]); + + // Combined fetching data to reduce multiple useEffect hooks + useEffect(() => { + const fetchData = async () => { + if (!access_token) return; + + try { + // Fetch Cards + const cardData = await getCards(access_token); + setGetCards(cardData.content); + + // Fetch Balance + const currentUser = await getCurrentUser(access_token); + setCurrentUser(currentUser); + setBalance(currentUser.accountBalance); + + // Fetch Income + const incomeData = await getTransactionIncomes(0, 1, access_token); + const totalIncome = incomeData.data.content.reduce( + (sum: number, item: any) => sum + item.amount, + 0 + ); + setIncome(String("0")); + setIncome(String(totalIncome)); + + // Fetch Expense + const expenseData = await getTransactionsExpenses(0, 1, access_token); + const totalExpense = expenseData.data.content.reduce( + (sum: number, item: any) => sum + item.amount, + 0 + ); + setExpense("0"); + setExpense(String(totalExpense)); + + // Fetch Transactions + const transactionData:TransactionResponse = await getTransactions(0, 3, access_token) + setTransaction(transactionData.data.content) + + } catch (error) { + console.error("Error fetching data:", error); + } + }; + + fetchData(); + }, [access_token]); + + // Example data for the first ListCard + const ReusableCard: Column = { + icon: MdHome, + iconStyle: "text-[#FFBB38] bg-[#FFF5D9]", + data: [ + { + heading: "My Balance", + text: String(balance), + headingStyle: "text-sm font-bold text-nowrap text-[#718EBF]", + dataStyle: "text-xs text-nowrap", + }, + ], + }; + + // Example data for the second ListCard + const card1: Column = { + icon: MdAttachMoney, // Updating the icon + iconStyle: "text-[#396AFF] bg-[#E7EDFF]", // Updating the iconStyle + data: ReusableCard.data.map((item) => ({ + ...item, + text: String(income), + heading: "Income", // Updating the heading + })), + }; + + // Example data for the third ListCard + const card2: Column = { + icon: MdSettings, // Updating the icon + iconStyle: "text-[#FF82AC] bg-[#FFE0EB]", // Updating the iconStyle + data: ReusableCard.data.map((item) => ({ + ...item, + text: String(expense), + heading: "Expense", // Updating the heading + })), + }; + + // Example data for the fourth ListCard + const card3: Column = { + icon: MdAccountBalance, // Updating the icon + iconStyle: "text-[#16DBCC] bg-[#DCFAF8]", // Updating the iconStyle + data: ReusableCard.data.map((item) => ({ + ...item, + heading: "Total Savings", // Updating the heading + })), + }; + + + // First column with multiple data items + const ReusableLastTransaction: Column = { + icon: MdHome, + iconStyle: "text-[#FFBB38] bg-[#FFF5D9]", + data: [ + { + heading: "Spotify Subscription", + text: "25 Jan 2021", + headingStyle: "text-sm font-bold text-nowrap", + dataStyle: "text-xs text-nowrap text-[#718EBF]", + }, + { + heading: "-$150", + text: "", + headingStyle: "text-xs font-bold text-[#FE5C73]", + dataStyle: "text-xs text-nowrap", + }, + ], + }; + + // First transaction example + const transaction1: Column = { + icon: MdAccountBalance, // Different icon + iconStyle: "text-[#16DBCC] bg-[#DCFAF8]", // Different iconStyle + data: ReusableLastTransaction.data.map((item, index) => ({ + ...item, + heading: index === 0 ? "Mobile Services" : item.heading, // Custom heading for the first item + })), + }; + + const transaction2: Column = { + icon: MdAttachMoney, // Updating the icon + iconStyle: "text-[#16DBCC] bg-[#DCFAF8]", // Updating the iconStyle + data: ReusableLastTransaction.data.map((item, index) => ({ + ...item, + heading: index === 0 ? "Emilly Wilson " : "+$780", + headingStyle: + index === 0 ? item.headingStyle : "text-xs font-bold text-[#16DBAA]", + })), + }; + + + + + // Map transaction data to ListCard columns + const createTransactionColumn = (transaction: TransactionData): Column => { + return { + icon: MdAccountBalance, // Default icon, you can customize based on type + iconStyle: "text-[#16DBCC] bg-[#DCFAF8]", // Default iconStyle, you can customize based on type + data: [ + { + heading: transaction.description, + text: formatDate(transaction.date), + headingStyle: "text-sm font-bold text-nowrap", + dataStyle: "text-xs text-nowrap text-[#718EBF]", + }, + { + heading: transaction.amount < 0 ? `-${Math.abs(transaction.amount)}` : `+${transaction.amount}`, + text: transaction.receiverUserName || "unknown source", + headingStyle: `text-xs font-bold ${transaction.amount < 0 ? "text-[#FE5C73]" : "text-[#16DBAA]"}`, + dataStyle: "text-xs text-nowrap", + }, + ], + }; + }; + + + if (loading) { + return ; + } + + // Don't render anything while loading + return ( + <> +
+
+
+ {balance || income == "0" ? ( + + ) : ( + + )} + + {income || income == "0" ? ( + + ) : ( + + )} + {expense || expense == "0" ? ( + + ) : ( + + )} + {balance || balance == "0" ? ( + + ) : ( + + )} +
+
+ +
+
+ + Last Transaction + +
+ {transaction.length > 0 ? ( + transaction.slice(0, 3).map((txn, index) => ( + + )) + ) : ( + + )} +
+
+ +
+
+ + My Card + + + See All + +
+ {getCard ? ( + getCard.map((items) => ( + + )) + ) : ( +
+
+
+
+

+

+
+
+
+ +
+
+

+

+
+
+

+

+
+
+ +
+
+
+
+
+
+
+
+
+ )} +
+
+ +
+
+ + Debit & Credit Overview + + +
+
+ + Invoice Sent + +
+ + + + +
+
+
+
+ + ); +}; +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "short", + day: "numeric", + }; + + return date.toLocaleDateString("en-US", options); +}; + +export default Page; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/options.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/options.ts new file mode 100644 index 000000000..06fa242c6 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/options.ts @@ -0,0 +1,59 @@ +import CredentialsProvider from "next-auth/providers/credentials"; +import { NextAuthOptions } from 'next-auth'; +import { login } from "@/lib/api/authenticationController"; +export const options: NextAuthOptions = { + session: { + strategy: "jwt", // Use JWT for session strategy + }, + + + providers: [ + CredentialsProvider({ + name: "credentials", + credentials: { + userName: { label: "Username", type: "string" }, + password: { label: "Password", type: "string" } + }, + + async authorize(credentials, req) { + try { + const response = await login(credentials as { userName: string; password: string }); + if (response.success) { + return response.data; // Return the user data object + } else { + return null; + } + } catch (error) { + console.error('Authorization error:', error); + return null; + } + }, + }) + + ], + + pages:{ + signIn: '/api/auth/signin' + // signUp: '/api/auth/signup', + }, + callbacks: { + // Store the user information in the JWT token + + async jwt({ token, user }: any) { + // Decode the access token to check expiry + if (user) { + token.access_token = user.access_token; + token.data = user.data; // Assuming the user object has a 'name' field + token.refresh_token = user.refresh_token; + } + return token + }, + // Make custom user data available in the session + async session({ session, token }: any) { + session.user.access_token = token.access_token; + session.user.data = token.data; + session.user.refresh_token = token.refresh_token; + return session; + }, + }, +}; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/route.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 000000000..e0317d663 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,5 @@ +import NextAuth from "next-auth"; +import { options } from "./options"; + +const handler = NextAuth(options); +export { handler as GET, handler as POST }; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/token/RefreshToken.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/token/RefreshToken.ts new file mode 100644 index 000000000..c05a893bd --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/[...nextauth]/token/RefreshToken.ts @@ -0,0 +1,28 @@ +import {jwtDecode, JwtPayload } from "jwt-decode"; +import { getSession } from "next-auth/react"; +import { refreshToken } from "@/lib/api/authenticationController"; +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +export default async function Refresh(): Promise { + const session = (await getSession()) as SessionDataType | null + if (!session?.user?.access_token || !session?.user?.refresh_token) { + return "" + } + const accessToken = session.user.access_token; + const refreshTokenValue = session.user.refresh_token; + + const decodedToken = jwtDecode(accessToken); + const currentTime = Date.now() / 1000; + const expiry = decodedToken.exp; + console.log(accessToken, "access token"); + + return accessToken +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/components/Forms.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/components/Forms.tsx new file mode 100644 index 000000000..0c0c2ba2a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/components/Forms.tsx @@ -0,0 +1,115 @@ +"use client"; +import { useForm } from "react-hook-form"; +import { DevTool } from "@hookform/devtools"; +import { BsExclamationCircle } from "react-icons/bs"; +import { FaGoogle, FaGithub } from "react-icons/fa"; +import { signIn } from "next-auth/react"; +import { useRouter } from "next/navigation"; +type FormValues = { + username: string; + password: string; +}; + +const Contact = () => { + const form = useForm(); + const { register, control, handleSubmit, formState } = form; + const { errors } = formState; + const router = useRouter() + + const onSubmit = async (data: FormValues) => { + await signIn("credentials", { + redirect: true, + // callbackUrl: "/dashboard", + userName: data.username, + password: data.password, + }); + }; + + + const route = () => { + router.push("/api/auth/signup") + } + + return ( +
+
+
+ + Welcome Back, + +
+
+ + +
+ +
+
+
+
+ + + {errors.username && ( +
+ +

{errors.username.message}

+
+ )} +
+
+ + + {errors.password && ( +
+ +

+ {errors.password.message} +

+
+ )} +
+
+ +
+
+
+
+ Don’t have an account? + +
+ +
+
+
+ ); +}; + +export default Contact; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/page.tsx new file mode 100644 index 000000000..f0afea115 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signin/page.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { getServerSession } from 'next-auth' +import { options } from '../[...nextauth]/options' +import { redirect } from 'next/navigation' +import Forms from './components/Forms' +const page = async () => { + const session = await getServerSession(options) + if(session){ + redirect("/") + } + return ( + + ) +} + +export default page diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageOne.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageOne.tsx new file mode 100644 index 000000000..3fbaf8013 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageOne.tsx @@ -0,0 +1,238 @@ +import React from "react"; +import DatePicker from "react-datepicker"; +import { useFormContext, Controller } from "react-hook-form"; +import { BsExclamationCircle } from "react-icons/bs"; +import "react-datepicker/dist/react-datepicker.css"; + +const PageOne = () => { + const { control } = useFormContext(); + + return ( +
+
+ {/* Welcome Text */} +

+ Basic Information +

+ + {/* Form Container */} +
+ {/* Name Field */} +
+ + ( + <> + + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+ + {/* Email Field */} +
+ + ( + <> + + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+ + {/* Date of Birth Field */} +
+ + ( + <> + field.onChange(date)} + className="border border-gray-400 rounded-lg py-2 px-5 w-full dark:border-gray-600 dark:bg-[#313244] dark:text-[#cdd6f4] dark:focus:bg-[#313244] dark:focus:border-[#4640DE] dark:focus:text-[#cdd6f4]" + /> + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+ + {/* Username Field */} +
+ + ( + <> + + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+ + {/* Password Field */} +
+ + ( + <> + + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+ + {/* Confirm Password Field */} +
+ + + value === password || "Passwords must match", + }} + render={({ field, fieldState: { error } }) => ( + <> + + {error && ( +
+ + {error.message} +
+ )} + + )} + /> +
+
+
+
+ ); +}; + +export default PageOne; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageThree.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageThree.tsx new file mode 100644 index 000000000..24ac86964 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageThree.tsx @@ -0,0 +1,193 @@ +import React, { useState } from "react"; +import { useFormContext, Controller } from "react-hook-form"; +import Image from "next/image"; +import { FaPencilAlt } from "react-icons/fa"; +import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; +import { v4 } from "uuid"; +import { storage } from "@/app/firebase"; +import { BsExclamationCircle } from "react-icons/bs"; + +type ToggleType = { + name: string; + label: string; + control: any; +}; + +// Reusable Toggle Switch Component +const ToggleSwitch = ({ name, label, control }: ToggleType) => ( +
+ + ( +
onChange(!value)} + > + +
+ )} + /> +
+); + +// Reusable error message component +const ErrorMessage = ({ message }: any) => ( +
+ + {message} +
+); + +const PageThree = () => { + const { control, setValue, formState: { errors } } = useFormContext(); + const [user, setUser] = useState({ + profilePicture: "", + }); + + const [loading, setLoading] = useState(false); // Loading state for image upload + const [error, setError] = useState(""); // Error state for image upload + + return ( +
+

+ Account Settings +

+ + {/* Profile Picture Upload */} +
+ {loading && ( +
+
Uploading...
+
+ )} + Profile Picture + + {/* Hidden file input for selecting a new profile picture */} + ) => { + const file = e.target.files?.[0]; // Check if files exist + if (file) { + setValue("profilePicture", file); + setLoading(true); + + // Validate file type and size before upload + if (!file.type.startsWith("image/")) { + setError("Please select a valid image file."); + setLoading(false); + return; + } + if (file.size > 5 * 1024 * 1024) { + setError("File size should be less than 5MB."); + setLoading(false); + return; + } + + // Immediately upload the image to Firebase + try { + const imageRef = ref(storage, `images/${file.name}-${v4()}`); + await uploadBytes(imageRef, file); + + // Get the download URL after the image is uploaded + const downloadUrl = await getDownloadURL(imageRef); + + // Update the profile picture in the form state and in the UI + setValue("profilePicture", downloadUrl); + setUser((prev) => ({ ...prev, profilePicture: downloadUrl })); + setError(""); // Clear any previous errors + } catch (error) { + setError("Error uploading image. Please try again."); + console.error("Error uploading image:", error); + } finally { + setLoading(false); + } + } + }} + style={{ display: "none" }} // Hide the input + id="profilePictureInput" + /> + + {/* Label for the file input, styled as an edit icon */} + +
+ + {/* Error Message for Image Upload */} + {error && } + + {/* Currency Field */} +
+ + ( + <> + + {error && } + + )} + /> +
+ + {/* Toggles */} + + + + +
+ ); +}; + +export default PageThree; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageTwo.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageTwo.tsx new file mode 100644 index 000000000..40e3cd651 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/PageTwo.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import { useFormContext, Controller } from "react-hook-form"; +import { BsExclamationCircle } from "react-icons/bs"; + +// Reusable error message component +const ErrorMessage = ({ message }: any) => ( +
+ + {message} +
+); + +const PageTwo = () => { + const { control } = useFormContext(); + + return ( +
+
+

+ Your Address +

+ + {/* Reusable Field Component */} + {[ + { name: "city", label: "City", placeholder: "Enter your city" }, + { + name: "presentAddress", + label: "Present Address", + placeholder: "Enter your present address", + }, + { name: "country", label: "Country", placeholder: "Enter your country" }, + { + name: "permanentAddress", + label: "Permanent Address", + placeholder: "Enter your permanent address", + }, + { + name: "postalCode", + label: "Postal Code", + placeholder: "Enter your postal code", + }, + { name: "timeZone", label: "Time Zone", placeholder: "Enter your time zone" }, + ].map(({ name, label, placeholder }) => ( +
+ + ( + <> + + {error && } + + )} + /> +
+ ))} +
+
+ ); +}; + +export default PageTwo; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/SignUpForm.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/SignUpForm.tsx new file mode 100644 index 000000000..65daa22d9 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/components/SignUpForm.tsx @@ -0,0 +1,113 @@ +"use client"; +import React, { useState } from "react"; +import { useForm, FormProvider, SubmitHandler } from "react-hook-form"; +import PageOne from "./PageOne"; +import PageTwo from "./PageTwo"; +import PageThree from "./PageThree"; +import { register } from "@/lib/api/authenticationController"; +import { RegisterRequest, RegisterResponse } from "@/types/authenticationController.interface"; +import { useRouter } from "next/navigation"; + +type SignUpFormData = { + name: string; + email: string; + password: string; + city: string; + presentAddress: string; + country: string; + permanentAddress: string; + postalCode: string; + timeZone: string; + currency: string; + sentOrReceiveDigitalCurrency: boolean; + receiveMerchantOrder: boolean; + accountRecommendations: boolean; + twoFactorAuthentication: boolean; + profilePicture?: File | string; + dateOfBirth: string; + username: string; +}; + +const SignUpForm = () => { + const methods = useForm(); + const [page, setPage] = useState(1); + const router = useRouter(); + + const onSubmit: SubmitHandler = async (data) => { + const registerRequest: RegisterRequest = { + name: data.name, + email: data.email, + dateOfBirth: data.dateOfBirth, + permanentAddress: data.permanentAddress, + postalCode: data.postalCode, + username: data.username, + password: data.password, + presentAddress: data.presentAddress, + city: data.city, + country: data.country, + profilePicture: + typeof data.profilePicture === "string" ? data.profilePicture : "", + preference: { + currency: data.currency, + sentOrReceiveDigitalCurrency: data.sentOrReceiveDigitalCurrency, + receiveMerchantOrder: data.receiveMerchantOrder, + accountRecommendations: data.accountRecommendations, + timeZone: data.timeZone, + twoFactorAuthentication: data.twoFactorAuthentication, + }, + }; + + console.log("Final Data:", registerRequest); + const d: RegisterResponse = await register(registerRequest); + console.log(d) + alert("Registered Successfully"); + router.push("/api/auth/signin"); + }; + + const nextPage = () => setPage((prev) => prev + 1); + const prevPage = () => setPage((prev) => prev - 1); + + return ( +
+
+ +
+ {page === 1 && } + {page === 2 && } + {page === 3 && } + +
+ {page > 1 && ( + + )} + {page < 3 ? ( + + ) : ( + + )} +
+ +
+
+
+ ); +}; + +export default SignUpForm; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/page.tsx new file mode 100644 index 000000000..cf5751475 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/api/auth/signup/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import SignUpForm from './components/SignUpForm' +const page = () => { + return ( + + ) +} + +export default page diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/BankServiceList.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/BankServiceList.tsx new file mode 100644 index 000000000..e6295b745 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/BankServiceList.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +interface Service { + name: string; + description: string; +} + +interface BankServiceListProps { + logoBgColor?: string; + logoSvg?: React.ReactNode; + serviceName?: string; + serviceDescription?: string; + additionalServices?: Service[]; + viewDetailsLink?: string; +} + +const BankServiceList: React.FC = ({ + logoBgColor = "bg-pink-100", + logoSvg = ( + + {/* Default SVG content */} + + ), + serviceName = "Business loans", + serviceDescription = "It is a long established", + additionalServices = [], + viewDetailsLink = "#", +}) => { + return ( +
+ {/* Mobile Layout */} +
+
+
+ roller + {logoSvg} +
+
+

{serviceName}

+

{serviceDescription}

+
+
+ View Details +
+ + {/* Web Layout */} +
+
+
+ {logoSvg} +
+
+

{serviceName}

+

{serviceDescription}

+
+
+
+ {additionalServices.map((service, index) => ( +
+

{service.name}

+

{service.description}

+
+ ))} +
+ View Details +
+
+ ); +}; + +export default BankServiceList; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/InformationCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/InformationCard.tsx new file mode 100644 index 000000000..fa0a698eb --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/components/InformationCard.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +interface InformationCardProps { + logoBgColor?: string; + logo: React.ReactNode; + title: string; + description: string; + cardBgColor?: string; +} + +const InformationCard: React.FC = ({ + logoBgColor = '#E7EDFF', + logo, + title, + description, + cardBgColor = 'bg-green-200', +}) => { + return ( +
+ {/* Mobile Layout */} +
+
+ {logo} +
+
+

{title}

+

{description}

+
+
+ + {/* Web Layout */} + +
+ ); +}; + +export default InformationCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/page.tsx new file mode 100644 index 000000000..f9abcdaf7 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingServices/page.tsx @@ -0,0 +1,199 @@ +'use client' +import React, { useEffect, useState } from 'react' +import { useRouter } from "next/navigation"; +import InformationCard from './components/InformationCard' +import BankServiceList from './components/BankServiceList' +import { getSession } from 'next-auth/react'; +import { IconType } from 'react-icons'; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { getBankServices } from '@/lib/api/bankServiceControl'; + +const shimmerClass = 'bg-gray-200 animate-pulse'; + +type infoType = { + id: string; + name: string; + details: string; + numberOfUsers: number; + status: string; + type: string; + icon: string; +} + +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; +}; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +const Page = () => { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [bankInfo, setBankServices] = useState([]); + + useEffect(() => { + const fetchSession = async () => { + try { + + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push(`./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}`); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, [router]); + console.log("bayba", access_token) + useEffect(() => { + const addingData = async () => { + if (!access_token) return; + if (access_token) { + const bankServices = await getBankServices(access_token, 0, 100); + console.log("Fetching Completed", bankServices.data.content); + setBankServices(bankServices.data.content); // Set the content array + } + }; + addingData(); + setLoading(false); + }, [access_token]); + + if (loading) { + // Render shimmer effect while loading + return ( +
+
+
+
+
+
+
+ ); + } + + + + + + // if (!session) { + // router.push(`./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}`); + // return null; + // } + + const getBackgroundColor = (index: number) => { + const colors = ['bg-[#FFE0EB]', 'bg-[#E0F7FA]', 'bg-[#FFF9C4]']; + return colors[index % colors.length]; + }; + + return ( +
+ + + +
+ + + + + + + + + + + + } + title="Life Insurance" + description="Unlimited protection" + cardBgColor="bg-[#ffffff]" + /> + + + + + } + title="Shopping" + description="Buy. Think. Grow" + cardBgColor="bg-[#ffffff]" + /> + + + + + + + } + title="Safety" + description="We are your allies" + cardBgColor="bg-[#ffffff]" + /> +
+

Bank Services List

+
+ {bankInfo.map((item, index) => ( + + + + )} + serviceName={item.name} + serviceDescription={item.details} + additionalServices={[ + { name: `Users: ${item.numberOfUsers}`, description: "" }, + { name: `Type: ${item.type}`, description: "" }, + { name: `Status: ${item.status}`, description: ""}, + ]} + viewDetailsLink={`https://example.com/details/${index}`} + /> + ))} +
+
+ ); +} + +export default Page; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/bankingSettings/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingSettings/page.tsx new file mode 100644 index 000000000..1f424e49a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/bankingSettings/page.tsx @@ -0,0 +1,221 @@ +"use client"; +import React, { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { getSession } from "next-auth/react"; +import Tabs from '../components/Tabs'; +import NotificationToggle from '../components/NotificationToggle'; +import EditProfile from '../components/EditProfile'; +import SecuritySetting from '../components/SecuritySetting'; +import { getCurrentUser, userUpdatePreference } from '../../lib/api/userControl'; +import User, { Preference } from '../../types/userInterface'; +import Refresh from '@/app/api/auth/[...nextauth]/token/RefreshToken'; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +const SettingsPage: React.FC = () => { + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [session, setSession] = useState(null); + const [activeTab, setActiveTab] = useState('Edit Profile'); + const [user, setUser] = useState(null); + const [notifications, setNotifications] = useState({ + currency: "", + sentOrReceiveDigitalCurrency: true, + receiveMerchantOrder: false, + accountRecommendations: true, + timeZone: "", + twoFactorAuthentication: false, + }); + const [message, setMessage] = useState(null); + + useEffect(() => { + const fetchSessionAndUser = async () => { + setLoading(true); + + const sessionData = (await getSession()) as SessionDataType | null; + + if (sessionData && sessionData.user) { + setSession(sessionData); + try { + const userData = await getCurrentUser(sessionData.user.access_token); + setUser(userData); + setNotifications(userData.preference); + } catch (error) { + console.error("Error fetching user data:", error); + } + } else { + router.push(`./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}`); + } + setLoading(false); + }; + + fetchSessionAndUser(); + }, [router]); + + const handleTabChange = (tab: string) => setActiveTab(tab); + + const handleNotificationChange = (key: keyof Preference, value: boolean | string) => { + setNotifications(prev => ({ ...prev, [key]: value })); + }; + + const handleTextInputChange = (key: keyof Preference, value: string) => { + setNotifications(prev => ({ ...prev, [key]: value })); + }; + + const handlePreferencesUpdate = async (event: React.FormEvent) => { + event.preventDefault(); + setMessage(null); + try { + if (session?.user?.access_token) { + const accessToken = await Refresh(); + await userUpdatePreference(notifications, accessToken); + setMessage('Preferences updated successfully!'); + } + } catch (error) { + console.error('Error updating preferences:', error); + setMessage('Failed to update preferences. Please try again.'); + } + }; + + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ ); + } + + return ( +
+
+
+ + +
+ {activeTab === 'Preferences' && ( +
+
+
+ + handleTextInputChange('currency', e.target.value)} + /> +
+
+ + handleTextInputChange('timeZone', e.target.value)} + /> +
+
+ +
+ +
+ handleNotificationChange('sentOrReceiveDigitalCurrency', checked)} + /> + handleNotificationChange('receiveMerchantOrder', checked)} + + /> + handleNotificationChange('accountRecommendations', checked)} + + /> + +
+
+ + {message && ( +
+ {message} +
+ )} + +
+ +
+
+ )} + + {activeTab === 'Edit Profile' && } + {activeTab === 'Security' && } +
+
+
+
+ ); +}; + +export default SettingsPage; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Context/DarkModeContext.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Context/DarkModeContext.tsx new file mode 100644 index 000000000..010f323bb --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Context/DarkModeContext.tsx @@ -0,0 +1,37 @@ +// DarkModeContext.tsx +"use client" +// DarkModeContext.tsx +import React, { createContext, useState, useContext, ReactNode, useEffect } from 'react'; + +interface DarkModeContextType { + darkMode: boolean; + toggleDarkMode: () => void; +} + +const DarkModeContext = createContext(undefined); + +export const DarkModeProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [darkMode, setDarkMode] = useState(false); + + useEffect(() => { + document.body.classList.toggle('dark', darkMode); + }, [darkMode]); + + const toggleDarkMode = () => { + setDarkMode(prevMode => !prevMode); + }; + + return ( + + {children} + + ); +}; + +export const useDarkMode = () => { + const context = useContext(DarkModeContext); + if (context === undefined) { + throw new Error('useDarkMode must be used within a DarkModeProvider'); + } + return context; +}; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/EditProfile.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/EditProfile.tsx new file mode 100644 index 000000000..4d31438c2 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/EditProfile.tsx @@ -0,0 +1,411 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import Image from "next/image"; +import { FaPencilAlt, FaCaretDown } from "react-icons/fa"; +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import { useForm, Controller } from "react-hook-form"; +import User, { UserInfo } from "@/types/userInterface"; +import { + getCurrentUser, + getUserByUsername, + userUpdate, +} from "@/lib/api/userControl"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { storage } from "../firebase"; +import { + ref, + uploadBytes, + getDownloadURL, + deleteObject, +} from "firebase/storage"; +import { v4 } from "uuid"; +interface FormData { + name: string; + username: string; + email: string; + city: string; + dateOfBirth: Date | null; + presentAddress: string; + permanentAddress: string; + country: string; + postalCode: string; + profilePicture: string | File; +} + +const EditProfile = () => { + const { control, handleSubmit, setValue } = useForm(); + const [user, setUser] = useState(null); + const [accessToken, setAccessToken] = useState(""); + useEffect(() => { + const fetchData = async () => { + try { + const accessToken = await Refresh(); + setAccessToken(accessToken); + } catch (error) { + console.error("Error fetching token:", error); + } + }; + + fetchData(); + }, []); + + useEffect(() => { + const fetchData = async () => { + try { + if (accessToken) { + const currentUser = await getCurrentUser(accessToken); + const userData = await getUserByUsername( + currentUser.username, + accessToken + ); + setUser(userData); + + // Populate form fields + setValue("name", userData.name || ""); + setValue("username", userData.username || ""); + setValue("email", userData.email || ""); + setValue("city", userData.city || ""); + setValue( + "dateOfBirth", + userData.dateOfBirth ? new Date(userData.dateOfBirth) : null + ); + setValue("presentAddress", userData.presentAddress || ""); + setValue("permanentAddress", userData.permanentAddress || ""); + setValue("country", userData.country || ""); + setValue("postalCode", userData.postalCode || ""); + } + } catch (error) { + console.error("Error fetching user data:", error); + } + }; + + fetchData(); + }, [accessToken, setValue]); + + const onSubmit = async (data: FormData) => { + try { + if (data.profilePicture != null) { + // Update the data with the download URL for profilePicture + const updatedData = { + ...data, + profilePicture: String(data.profilePicture), + dateOfBirth: data.dateOfBirth + ? data.dateOfBirth.toISOString().split("T")[0] + : null, + }; + + // Send the updated data to userUpdate + await userUpdate(updatedData, accessToken); + } + + console.log("Profile updated successfully"); + alert("Profile Edited Successfully"); + } catch (error) { + console.error("Error updating profile:", error); + } + }; + + return ( + <> +
+
+ Profile Picture + + {/* Hidden file input for selecting a new profile picture */} + ) => { + const file = e.target.files?.[0]; // Check if files exist + if (file) { + setValue("profilePicture", file); + + // Immediately upload the image to Firebase + try { + const imageRef = ref(storage, `images/${file.name}-${v4()}`); + await uploadBytes(imageRef, file); + + // Get the download URL after the image is uploaded + const downloadUrl = await getDownloadURL(imageRef); + console.log("dowloaded the url ", downloadUrl); + + // Update the profile picture in the form state and in the UI + setValue("profilePicture", downloadUrl); + setUser( + (prev) => prev && { ...prev, profilePicture: downloadUrl } + ); + } catch (error) { + console.error("Error uploading image:", error); + } + } + }} + style={{ display: "none" }} // Hide the input + id="profilePictureInput" + /> + + {/* Label for the file input, styled as an edit icon */} + +
+
+ +
+
+
+
+ {/* Name Goes In Here */} +
+ + ( + + )} + /> +
+ + {/* Username Goes In Here */} +
+ + ( + + )} + /> +
+
+ +
+ {/* Email Goes In Here */} +
+ + ( + + )} + /> +
+ + {/* City Goes In Here */} +
+ + ( + + )} + /> +
+
+ +
+ {/* Date Of Birth Goes In Here */} +
+ +
+ ( + onChange(date)} + placeholderText="Date Of Birth" + className="w-full border border-[#DFEAF2] focus:outline-[#DFEAF2] focus:border-[#DFEAF2] rounded-xl py-3 px-6 placeholder:text-[#718EBF] bg-white dark:border-gray-600 dark:focus:outline-none dark:bg-[#313244] dark:text-[#cdd6f4] dark:focus:bg-[#313244] dark:focus:border-[#4640DE] dark:focus:text-[#cdd6f4]" + dateFormat="MMMM d, yyyy" + id="datePicker" + /> + )} + /> + +
+
+ + {/* Present Address Goes In Here */} +
+ + ( + + )} + /> +
+
+ +
+ {/* Permanent Address Goes In Here */} +
+ + ( + + )} + /> +
+ + {/* Country Goes In Here */} +
+ + ( + + )} + /> +
+ + {/* Country Goes In Here */} +
+ + ( + + )} + /> +
+
+ +
+ +
+
+
+
+ + ); +}; + +export default EditProfile; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navbar.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navbar.tsx new file mode 100644 index 000000000..eda00652e --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navbar.tsx @@ -0,0 +1,172 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { MdOutlineSearch } from "react-icons/md"; +import { GiHamburgerMenu } from "react-icons/gi"; +import { + IoLogOutOutline, + IoMoonOutline, + IoSettingsOutline, +} from "react-icons/io5"; +import { IoMdNotificationsOutline } from "react-icons/io"; +import { useRouter } from "next/navigation"; +import { signOut } from "next-auth/react"; +interface Props { + handleClick: () => void; + toggleDarkMode: () => void; +} +import Image from "next/image"; +import { UserInfo } from "@/types/userInterface"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { getCurrentUser, getUserByUsername } from "@/lib/api/userControl"; +const Navbar = ({ handleClick, toggleDarkMode }: Props) => { + const route = useRouter(); + const [user, setUser] = useState(null); + const [accessToken, setAccessToken] = useState(""); + + useEffect(() => { + const fetchData = async () => { + try { + const accessToken = await Refresh(); + setAccessToken(accessToken); + } catch (error) { + console.error("Error fetching token:", error); + } + }; + + fetchData(); + }, []); + + useEffect(() => { + const fetchData = async () => { + try { + if (accessToken) { + const currentUser = await getCurrentUser(accessToken); + const userData = await getUserByUsername( + currentUser.username, + accessToken + ); + setUser(userData); + } + } catch (error) { + console.error("Error fetching user data:", error); + } + }; + + fetchData(); + }, [accessToken]); + console.log("Profile Picture", user?.profilePicture); + return ( +
+
+
+ +
+
+ Overview +
+ +
+
+ + +
+ +
+
route.push("./bankingSettings")} + > + +
+
+ +
+
+ +
+
{ + signOut(); + }} + > + +
+
+
+ Profile +
+
+
+ +
+ + +
+
+ ); +}; + +export const NavBarLoading = () => { + return ( +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ ); +}; + +export default Navbar; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navigation.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navigation.tsx new file mode 100644 index 000000000..572fd4e55 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Navigation.tsx @@ -0,0 +1,99 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import Navbar, { NavBarLoading } from "./Navbar"; +import Sidebar, { SidebarLoading } from "./Sidebar"; +import { getSession } from "next-auth/react"; +import Loading from "../accounts/components/Loading"; +import { useDarkMode } from "./Context/DarkModeContext"; + +interface Props { + children: React.ReactNode; +} + +const Navigation: React.FC = ({ children }) => { + const [toggle, setToggle] = useState(false); + const [session, setSession] = useState(false); + const [loading, setLoading] = useState(true); + const { darkMode, toggleDarkMode } = useDarkMode(); // Use dark mode context + + useEffect(() => { + const fetchSession = async () => { + const sessionData = await getSession(); + if (sessionData?.user) { + setSession(true); + } + setLoading(false); + }; + + fetchSession(); + }, []); + + return ( + <> + {loading ? ( +
+ +
+
+ +
+ +
+
+ ) : ( +
+ {session && ( +
+ { + setToggle(!toggle); + }} + /> +
+ )} + {toggle && ( +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ )} + +
+ {session && ( +
+ { + setToggle(!toggle); + }} + toggleDarkMode={toggleDarkMode} + /> +
+ )} + {children} +
+
+ )} + + ); +}; + +export default Navigation; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/NotificationToggle.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/NotificationToggle.tsx new file mode 100644 index 000000000..489c0bbec --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/NotificationToggle.tsx @@ -0,0 +1,49 @@ +"use client"; +import React, { useState } from "react"; + +interface NotificationToggleProps { + id: string; + label: string; + checked: boolean; + onChange: (checked: boolean) => void; +} + +const NotificationToggle: React.FC = ({ id, label, checked, onChange }) => { + const [enabled, setEnabled] = useState(checked); + + const handleToggle = () => { + const newChecked = !enabled; + setEnabled(newChecked); + onChange(newChecked); + }; + + return ( +
+ + + + +
+ ); +}; + +export default NotificationToggle; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/BarChart.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/BarChart.tsx new file mode 100644 index 000000000..749048d6b --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/BarChart.tsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; +import { Bar } from 'react-chartjs-2'; +import { Chart as ChartJS, BarElement, CategoryScale, LinearScale, Tooltip, Legend, TooltipItem } from 'chart.js'; +import { getBalanceHistory } from '@/lib/api/transactionController'; // Import the function + +interface BalanceHistoryData { + time: string; + value: number; +} + +ChartJS.register(BarElement, CategoryScale, LinearScale, Tooltip, Legend); + +// Utility function to get the last six months' labels +const getLastSixMonthsLabels = (): string[] => { + const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + const currentMonth = new Date().getMonth(); // Get the current month (0-11) + const labels = []; + + for (let i = 5; i >= 0; i--) { + const monthIndex = (currentMonth - i + 5) % 12; + labels.push(monthNames[monthIndex]); + } + + return labels; +}; + +// Shimmer component with vertical bars +const Shimmer = () => { + return ( +
+ {[...Array(6)].map((_, index) => ( +
+ ))} +
+ ); +}; + +const BarChart: React.FC<{ token: string }> = ({ token }) => { + const [chartData, setChartData] = useState([]); + const [loading, setLoading] = useState(true); + const [labels, setLabels] = useState(getLastSixMonthsLabels()); + + useEffect(() => { + const fetchBalanceHistory = async () => { + try { + const response = await getBalanceHistory(token); // Fetching data for the last 6 months + const data = response.data; + + + const sortedData = data + .sort((a: BalanceHistoryData, b: BalanceHistoryData) => new Date(a.time).getMonth() - new Date(b.time).getMonth()) + .map((entry: BalanceHistoryData) => entry.value + 1000); + + setChartData(sortedData.length > 0 ? sortedData : [0, 0, 0, 0, 0, 0]); + } catch (error) { + console.error('Error fetching balance history:', error); + } finally { + setLoading(false); + } + }; + + fetchBalanceHistory(); + }, [token]); + + const maxValueIndex = chartData.indexOf(Math.max(...chartData)); + + const data = { + labels: labels, + datasets: [ + { + label: 'Balance History', + data: chartData, + backgroundColor: chartData.map((_, index) => + index === maxValueIndex ? 'rgba(0, 204, 204, 0.8)' : 'rgba(0, 0, 0, 0.05)' + ), + hoverBackgroundColor: chartData.map((_, index) => + index === maxValueIndex ? 'rgba(0, 204, 204, 1)' : 'rgba(0, 204, 204, 0.5)' + ), + borderRadius: 10, + borderSkipped: false, + barPercentage: 0.6, + }, + ], + }; + + const options = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: false, + }, + tooltip: { + callbacks: { + label: function (tooltipItem: TooltipItem<'bar'>) { + const value = tooltipItem.raw as number; + return `$${value.toLocaleString()}`; + }, + }, + }, + }, + scales: { + x: { + grid: { + display: false, + }, + ticks: { + color: 'rgba(0, 0, 0, 0.5)', + }, + }, + y: { + beginAtZero: true, + display: false, + }, + }, + }; + + return ( +
+ {loading ? : } +
+ ); +}; + +export default BarChart; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Card.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Card.tsx new file mode 100644 index 000000000..c78e58e61 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Card.tsx @@ -0,0 +1,115 @@ +import React from 'react'; + +interface CardProps { + balance?: string; + cardHolder?: string; + validThru?: string; + cardNumber?: string; + filterClass?: string; + bgColor?: string; + textColor?: string; + iconBgColor?: string; + showIcon?: boolean; + loading?: boolean; +} + +const CreditCard: React.FC = ({ + balance, + cardHolder, + validThru, + cardNumber, + filterClass = "filter-white", + bgColor = '', + textColor = 'text-white', + iconBgColor = 'bg-opacity-10', + showIcon = true, + loading = false, +}) => { + + const isBlueGradient = bgColor.includes('#4C49ED') || bgColor.includes('#0A06F4'); + const ellipseImageSrc = isBlueGradient ? '/group17.svg' : '/group18.svg'; + const iconSrc = isBlueGradient ? '/sim.svg' : '/blackSim.svg'; + + const cardHolderTextColor = isBlueGradient ? 'text-[rgba(255,255,255,0.7)]' : 'text-[#718EBF]'; + + if (loading) { + return ( +
+
+
+
+

+

+
+
+
+ +
+
+

+

+
+
+

+

+
+
+ +
+
+
+
+
+
+
+
+
+ ); + } + + return ( +
+ {/* Set position relative here to contain the absolute positioned element */} + {/*
*/} +
+
+
+

Balance

+

{balance}

+
+ {showIcon && ( +
+ sim image +
+ )} +
+ +
+
+

CARD HOLDER

+

{cardHolder}

+
+
+

VALID THRU

+

{validThru}

+
+
+ +
+ {/* The absolute element stays within the bounds of the relative parent */} +
+
+ {cardNumber} +
+
+ ellipse background +
+
+
+
+ ); +}; + +export default CreditCard; + + diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Table.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Table.tsx new file mode 100644 index 000000000..af1f94847 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/Table.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +interface Column { + Header: string; + accessor: keyof T; + Cell?: (props: { [key: string]: any }) => React.ReactNode; +} + +interface TableProps { + columns: Column[]; + data: T[]; +} + +const Table = ({ columns, data }: TableProps) => { + return ( +
+
+ + {/* Table Header */} + + + {columns.map((column, index) => ( + + ))} + + + + {/* Table Body */} + + {(data || []).map((row, rowIndex) => ( + + {columns.map((column, colIndex) => ( + + ))} + + ))} + +
+ {column.Header} +
+ {column.Cell + ? column.Cell(row) + : (row[column.accessor] as unknown as React.ReactNode)} +
+
+
+ ); +}; + +export default Table; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/TransactionsList.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/TransactionsList.tsx new file mode 100644 index 000000000..1bc02df80 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/TransactionsList.tsx @@ -0,0 +1,295 @@ +import React, { useState, useEffect } from 'react'; + +interface Transaction { + description: string; + transactionId: string; + type: string; + receiverUserName?: string; + senderUserName?: string; + date: string; + amount?: string; + receipt?: string; +} + +interface TransactionsListProps { + transactions: Transaction[]; + loading: boolean; + activeTab: string; +} + +const TransactionsList: React.FC = ({ transactions, loading, activeTab }) => { + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + const totalPages = Math.ceil(transactions.length / itemsPerPage); + + // Adjust amount based on activeTab + const adjustedTransactions = transactions.map(transaction => { + let amount = parseFloat(transaction.amount || '0'); + + if (activeTab === 'Income') { + amount = Math.abs(amount); // Ensure amount is positive for income + } else if (activeTab === 'Expense') { + amount = -Math.abs(amount); // Ensure amount is negative for expense + } + + return { + ...transaction, + amount: amount.toFixed(2) // Format to 2 decimal places for consistency + }; + }); + + const paginatedTransactions = adjustedTransactions.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage + ); + + const handlePageClick = (pageNumber: number) => { + setCurrentPage(pageNumber); + }; + + const handleNextPage = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + } + }; + + const handlePreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + const renderPagination = () => { + const pageNumbers: (number | string)[] = []; + const maxPageNumbersToShow = 3; + + if (totalPages <= maxPageNumbersToShow) { + for (let i = 1; i <= totalPages; i++) { + pageNumbers.push(i); + } + } else { + let startPage = Math.max(1, currentPage - 2); + let endPage = Math.min(totalPages, currentPage + 2); + + if (currentPage <= 3) { + startPage = 1; + endPage = maxPageNumbersToShow; + } else if (currentPage >= totalPages - 2) { + startPage = totalPages - maxPageNumbersToShow + 1; + endPage = totalPages; + } + + for (let i = startPage; i <= endPage; i++) { + pageNumbers.push(i); + } + + if (startPage > 1) { + pageNumbers.unshift(1); + if (startPage > 2) { + pageNumbers.splice(1, 0, '...'); + } + } + + if (endPage < totalPages) { + pageNumbers.push(totalPages); + if (endPage < totalPages - 1) { + pageNumbers.splice(pageNumbers.length - 1, 0, '...'); + } + } + } + + return pageNumbers.map((pageNumber, index) => + typeof pageNumber === 'number' ? ( + + ) : ( + + {pageNumber} + + ) + ); + }; + + const columns = ['Description', 'Transaction ID', 'Type', 'Card', 'Date', 'Amount', 'Receipt']; + + return ( +
+ {loading ? ( +
+ {/* Shimmer effect for loading state */} +
+
+ + + + {columns.map((column, index) => ( + + ))} + + + + {[...Array(5)].map((_, rowIndex) => ( + + {columns.map((_, colIndex) => ( + + ))} + + ))} + +
+ {column} +
+
+
+
+
+ + {/* Mobile View */} +
+ {[...Array(5)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ ) : ( +
+ {/* Table rendering */} + {paginatedTransactions.length > 0 && ( + <> +
+
+ + + + {columns.map((column, index) => ( + + ))} + + + + {paginatedTransactions.map((row, rowIndex) => { + const amount = row.amount || '0'; + const isPositive = parseFloat(amount) > 0; + + return ( + + + + + + + + + + ); + })} + +
+ {column} +
+
+
+ {isPositive +
+ {row.description} +
+
{row.transactionId}{row.type}{row.receiverUserName || 'Unknown'}{row.date} + + {isPositive ? `+${amount}` : `${amount}`} + + + +
+
+
+ + {/* Mobile View */} +
+ {paginatedTransactions.map((transaction, index) => { + const amount = transaction.amount || '0'; + const isPositive = parseFloat(amount) > 0; + + return ( +
+
+
+
+ {isPositive +
+
+ {transaction.description} +
{transaction.date}
+
+
+ + {isPositive ? `+${amount}` : `${amount}`} + +
+
+ ); + })} +
+ + )} + + {/* Pagination */} +
+ + {renderPagination()} + +
+
+ )} +
+ ); +}; + +export default TransactionsList; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/WhiteCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/WhiteCard.tsx new file mode 100644 index 000000000..efa731dca --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Page2/WhiteCard.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import Card from './Card'; + +const WhiteCard = () => { + return ( + + ); +}; + +export default WhiteCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/SecuritySetting.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/SecuritySetting.tsx new file mode 100644 index 000000000..524899037 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/SecuritySetting.tsx @@ -0,0 +1,208 @@ +"use client"; +import React, { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { + userUpdatePreference, + getCurrentUser, +} from "../../lib/api/userControl"; +import { getSession } from "next-auth/react"; +import { changePassword } from "../../lib/api/authenticationController"; +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +const HeadingLabel = ({ label }: { label: string }) => { + return ( +

+ {label} +

+ ); +}; + +const InputLabel = ({ label, htmlFor }: { label: string; htmlFor: string }) => { + return ( + + ); +}; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +const ToggleSwitch = ({ + handleToogle, + enabled, +}: { + enabled: boolean; + handleToogle: Function; +}) => { + const toggleSwitch = () => { + handleToogle(!enabled); + }; + + return ( +
+ + + + +
+ ); +}; + +interface SecurityFormInputs { + old_password: string; + new_password: string; +} + +const SecuritySetting = () => { + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm(); + const router = useRouter(); + + const [enabled, setEnabled] = useState(false); + + useEffect(() => { + const fetchSession = async () => { + const sessionData = (await getSession()) as SessionDataType | null; + if (sessionData && sessionData.user) { + setSession(sessionData.user); + setLoading(false); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + }; + + fetchSession(); + }, [router]); + + const handleToogle = (value: boolean) => { + setEnabled(value); + }; + + const onSubmit = async (data: SecurityFormInputs) => { + const user = await getCurrentUser(session?.access_token!); + user.preference.twoFactorAuthentication = enabled; + await userUpdatePreference(user.preference, session?.access_token!); + await changePassword( + { password: data.old_password, newPassword: data.new_password }, + session?.access_token! + ); + reset(); + }; + + if (loading) { + return null; + } + + return ( +
+
+ +
+ +
+ + +
+ + + {errors.old_password && ( + + {errors.old_password.message} + + )} +
+
+ + + value !== "" || + "New Password cannot be the same as the old password", + })} + /> + {errors.new_password && ( + + {errors.new_password.message} + + )} +
+
+ +
+ +
+ ); +}; + +export default SecuritySetting; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Sidebar.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Sidebar.tsx new file mode 100644 index 000000000..1a566f231 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Sidebar.tsx @@ -0,0 +1,160 @@ +import React, { useState } from "react"; +import Image from "next/image"; +import { + MdHome, + MdAttachMoney, + MdAccountBalance, + MdAssessment, + MdCreditCard, + MdPayment, + MdStar, + MdSettings, +} from "react-icons/md"; +import { FaTimes } from "react-icons/fa"; +import { RiHandCoinLine } from "react-icons/ri"; +import { usePathname, useRouter } from "next/navigation"; +import SidebarElements from "./SidebarElements"; + +interface SidebarProps { + toggle: boolean; + handleClose: () => void; +} +const Sidebar = ({ toggle, handleClose }: SidebarProps) => { + +const map = new Map(); +map.set("/", "Dashboard"); +map.set("/dashboard", "Dashboard"); +map.set("/transactions", "Transactions"); +map.set("/accounts", "Accounts"); +map.set("/investments", "Investments"); +map.set("/creditCards", "Credit Cards"); +map.set("/loans", "Loans"); +map.set("/bankingServices", "Services"); +map.set("/bankingSettings", "Settings"); + +const pathName = usePathname(); +const router = useRouter(); +const [active, setActive] = useState(map.get(pathName)); + + const elements = [ + { + id: 1, + text: "Dashboard", + destination: "./dashboard", + icon: MdHome, + }, + { + id: 2, + text: "Transactions", + destination: "./transactions", + icon: MdAttachMoney, + }, + { + id: 3, + text: "Accounts", + destination: "./accounts", + icon: MdAccountBalance, + }, + { + id: 4, + text: "Investments", + destination: "./investments", + icon: MdAssessment, + }, + { + id: 5, + text: "Credit Cards", + destination: "./creditCards", + icon: MdCreditCard, + }, + { + id: 6, + text: "Loans", + destination: "./loans", + icon: RiHandCoinLine, + }, + { + id: 7, + text: "Services", + destination: "./bankingServices", + icon: MdPayment, + }, + { + id: 8, + text: "Settings", + destination: "./bankingSettings", + icon: MdSettings, + }, + ]; + + const handleNav = async (destination: string) => { + router.push(destination); + }; + const handleActive = (element: string) => { + setActive(element); + handleClose(); + }; + + return ( + <> +
+
+ Logo +
+ +
+ + {toggle && ( +
+
+
+ +
+ Logo +
+
+ +
+
+ )} + + ); +}; + +export const SidebarLoading = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default Sidebar; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/SidebarElements.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/SidebarElements.tsx new file mode 100644 index 000000000..d555869c1 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/SidebarElements.tsx @@ -0,0 +1,90 @@ +import { useRouter } from "next/navigation"; +import React from "react"; +import { IconType } from "react-icons"; +import { IoLogOutOutline, IoMoonOutline, IoSettingsOutline } from "react-icons/io5"; +import { useDarkMode } from "./Context/DarkModeContext"; +import { signOut } from "next-auth/react"; + +type ElementType = { + id: number; + text: string; + destination: string; + icon: IconType; +}; + +interface Props { + handleNav: (s: string) => void; + handleActive: (s: string) => void; + elements: ElementType[]; + active: string; +} + +const SidebarElements = ({ + handleActive, + handleNav, + elements, + active, +}: Props) => { + const route = useRouter(); + const { darkMode, toggleDarkMode } = useDarkMode(); // Use dark mode context + + return ( +
+ {elements.map((el) => ( +
+ +
+ ))} +
+
route.push("./bankingSettings")} + > + +
+ {/*
+ +
*/} +
+ +
+
{ + signOut(); + }} + > + +
+
+
+ ); +}; + +export default SidebarElements; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/Tabs.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Tabs.tsx new file mode 100644 index 000000000..a23959666 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/Tabs.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +interface TabsProps { + tabs: string[]; + activeTab: string; + onTabChange: (tab: string) => void; +} + +const Tabs: React.FC = ({ tabs, activeTab, onTabChange }) => { + return ( +
+ {tabs.map((tab) => ( + + ))} +
+ ); +}; + +export default Tabs; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/TextInput.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/TextInput.tsx new file mode 100644 index 000000000..421e9316c --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/TextInput.tsx @@ -0,0 +1,31 @@ +// components/TextInput.tsx +import React from 'react'; + +interface TextInputProps { + id: string; + label: string; + value: string; + placeholder?: string; + readOnly?: boolean; + onChange?: (event: React.ChangeEvent) => void; +} + +const TextInput: React.FC = ({ id, label, value, placeholder, readOnly = false, onChange }) => { + return ( +
+ + +
+ ); +}; + +export default TextInput; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/components/loadingprovider/ProgressBar.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/components/loadingprovider/ProgressBar.tsx new file mode 100644 index 000000000..458f834d6 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/components/loadingprovider/ProgressBar.tsx @@ -0,0 +1,23 @@ +"use client"; +import React, { useEffect } from "react"; +import { Next13ProgressBar } from "next13-progressbar"; + +const ProgressBar = ({ children }: { children: React.ReactNode }) => { + useEffect(() => { + console.log("ProgressBar rendered"); + }, []); + + return ( + <> + {children} + + + ); +}; + +export default ProgressBar; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/AddCardForm.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/AddCardForm.tsx new file mode 100644 index 000000000..4f0d1ed76 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/AddCardForm.tsx @@ -0,0 +1,162 @@ +import React from "react"; +import { useForm } from "react-hook-form"; +import { PostCardRequest } from "../../types/cardController.Interface"; +import { postCard } from "../../lib/api/cardController"; + +export const InputLabel = ({ label }: { label: string }) => { + return ( + + ); +}; + +const AddCardForm = ({ + access_token, + handleAddition, +}: { + access_token: string; + handleAddition: Function; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm(); + + const onSubmit_ = handleSubmit(async (data) => { + console.log(data.passcode); + data.passcode = "192930"; + const response = await postCard(data, access_token); + handleAddition(response); + reset(); + }); + + return ( +
+

+ Credit Card generally means a plastic card issued by Scheduled + Commercial Banks assigned to a Cardholder, with a credit limit, that can + be used to purchase goods and services on credit or obtain cash + advances. +

+ +
+
+
+ + + {errors.cardType && ( + + {errors.cardType.message} + + )} +
+
+ + + {errors.cardHolder && ( + + {errors.cardHolder.message} + + )} +
+
+
+
+ + + value <= 1000000 || "Balance cannot exceed $1,000,000", + })} + /> + {errors.balance && ( + + {errors.balance.message} + + )} +
+
+ + { + const today = new Date(); + const selectedDate = new Date(value); + return ( + selectedDate > today || + "Expiration date must be in the future" + ); + }, + })} + /> + {errors.expiryDate && ( + + {errors.expiryDate.message} + + )} +
+
+ + +
+
+ ); +}; + +export default AddCardForm; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSetting.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSetting.tsx new file mode 100644 index 000000000..d666bb8c1 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSetting.tsx @@ -0,0 +1,28 @@ +import Link from "next/link"; +import React, { ReactNode } from "react"; + +interface Props { + icon: ReactNode; + data: Array<[string, string]>; +} +const CardSetting = ({ icon, data }: Props) => { + return ( +
+
{icon}
+
+ {data.map((data, index) => { + return ( +
0 && "hidden"} lg:block`} key={index}> +

+ {data[0]} +

+

{data[1]}

+
+ ); + })} +
+
+ ); +}; + +export default CardSetting; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSettingList.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSettingList.tsx new file mode 100644 index 000000000..c0aec4ffc --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CardSettingList.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import CardSetting from "./CardSetting"; + +const CardList = () => { + return ( +
+ } + /> + } + /> + } + /> + } + /> + } + /> +
+ ); +}; + +export default CardList; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CreditCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CreditCard.tsx new file mode 100644 index 000000000..512572d1e --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/CreditCard.tsx @@ -0,0 +1,144 @@ +import Link from "next/link"; +import React, { ReactNode } from "react"; + +interface Props { + icon: ReactNode; + data: Array<[string, string]>; + handleDetail: Function; + card: Card; + initialValue: boolean; +} + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Card } from "@/types/cardController.Interface"; + +const DetailCard = ({ + handleClick, + initialValue, + card, +}: { + handleClick: Function; + initialValue: boolean; + card: Card; +}) => { + return ( + { + handleClick(false); + }} + > + +
+

+ Card Details +

+
+
+

+ Card Holder +

+

+ {card.cardHolder} +

+
+
+

+ Balance +

+

+ ${card.balance.toFixed(2)} +

+
+
+
+
+

+ Card Number +

+

+ {card.expiryDate} +

+
+
+

+ Card Type +

+

+ **** **** **** {card.semiCardNumber} +

+
+
+ +
+
+
+ ); +}; + +const CreditCard = ({ + icon, + data, + handleDetail, + card, + initialValue, +}: Props) => { + return ( + <> + +
+ {icon} +
+ {data.map((data, index) => { + return ( +
1 && "hidden"} lg:block`} key={index}> +

+ {data[0]} +

+

{data[1]}

+
+ ); + })} +
+ +
+ + ); +}; + +export default CreditCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/MainCreditCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/MainCreditCard.tsx new file mode 100644 index 000000000..f4e0b813f --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/MainCreditCard.tsx @@ -0,0 +1,50 @@ +import React from "react"; + +interface Props { + color1: string; + color2: string; + balance: number; + validThru: string; + cardNumber: string; + cardHolder: string; +} + +const MainCreditCard = ({ + color1, + color2, + balance, + validThru, + cardNumber, + cardHolder, +}: Props) => { + return ( +
+
+
+

Balance

+

+ {"$"} + {balance} +

+
+
+
+

CARD HOLDER

+

{cardHolder}

+
+
+

VALID THRU

+

{validThru}

+
+
+
+

{cardNumber}

+ +
+ +
+
+ ); +}; + +export default MainCreditCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/PieChart.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/PieChart.tsx new file mode 100644 index 000000000..19ee6ec4e --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/PieChart.tsx @@ -0,0 +1,189 @@ +"use client"; + +import * as React from "react"; +import { Label, Pie, PieChart, Sector } from "recharts"; +import { PieSectorDataItem } from "recharts/types/polar/Pie"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartStyle, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +const desktopData = [ + { month: "ABM Bank", desktop: 186, fill: "#16DBCC" }, + { month: "DBL Bank", desktop: 305, fill: "#4C78FF" }, + { month: "MCP Bank", desktop: 237, fill: "#FFBB38" }, + { month: "BRC Bank", desktop: 173, fill: "#FF82AC" }, +]; + +const chartConfig = { + visitors: { + label: "Visitors", + }, + desktop: { + label: "Desktop", + }, + mobile: { + label: "Mobile", + }, + january: { + label: "January", + color: "hsl(var(--chart-1))", + }, + february: { + label: "February", + color: "hsl(var(--chart-2))", + }, + march: { + label: "March", + color: "hsl(var(--chart-3))", + }, + april: { + label: "April", + color: "hsl(var(--chart-4))", + }, + may: { + label: "May", + color: "hsl(var(--chart-5))", + }, +} satisfies ChartConfig; + +export function PieChartPage() { + const id = "pie-interactive"; + const [activeMonth, setActiveMonth] = React.useState(desktopData[0].month); + + const activeIndex = React.useMemo( + () => desktopData.findIndex((item) => item.month === activeMonth), + [activeMonth] + ); + const months = React.useMemo(() => desktopData.map((item) => item.month), []); + + return ( +
+ + + + + + + } + /> + ( + + + + + )} + > + + + + + +
+ + +
+
+ ); +} + +export const ChartLabel = ({ + colors, + labels, +}: { + colors: string[]; + labels: string[]; +}) => { + return ( +
+ {colors.map((color, index) => ( +
+
+

+ {labels[index]} +

+
+ ))} +
+ ); +}; + +export default PieChartPage; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/Shimmer.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/Shimmer.tsx new file mode 100644 index 000000000..d04058ea0 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/Shimmer.tsx @@ -0,0 +1,69 @@ +export const ShimmerCreditCard = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export const ShimmerMainCreditCard = () => { + return ( +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ ); +}; + +export const ShimmerPieChartPage = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/page.tsx new file mode 100644 index 000000000..2f51e571d --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/creditCards/page.tsx @@ -0,0 +1,203 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import PieChart from "./PieChart"; +import CreditCard from "./CreditCard"; +import CardSettingList from "./CardSettingList"; +import AddCardForm from "./AddCardForm"; +import MainCreditCard from "./MainCreditCard"; +import Card from "../components/Page2/Card"; +import { Card as Card1 } from "../../types/cardController.Interface"; +import { getCards } from "@/lib/api/cardController"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { + ShimmerCreditCard, + ShimmerMainCreditCard, + ShimmerPieChartPage, +} from "./Shimmer"; + +const HeadingTitle = ({ title }: { title: string }) => { + return ( +

+ {title} +

+ ); +}; + +const CreditCards = () => { + const [accessToken, setAccessToken] = useState(""); + const [cards, setCards] = useState([]); + const [loading, setLoading] = useState(true); + const router = useRouter(); + const [searchTerm, setSearchTerm] = useState(""); + const [dialogOpen, setDialogOpen] = useState(false); + + const filteredCards = cards.filter( + (card) => + card.cardType.toLowerCase().includes(searchTerm.toLowerCase()) || + card.semiCardNumber.toString().includes(searchTerm) + ); + const handleDialogToogle = (value: boolean) => { + setDialogOpen(value); + }; + const convertToDate = (date: string) => { + const year = date.slice(2, 4); + const month = date.slice(5, 7); + + return month + "/" + year; + }; + useEffect(() => { + const fetchSession = async () => { + const access_token = await Refresh(); + if (access_token) { + setAccessToken(access_token); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/creditCards")}` + ); + } + }; + + fetchSession(); + }, [router]); + + useEffect(() => { + if (accessToken == "") { + return; + } + async function fetch() { + const data = await getCards(accessToken, 0, 700); + data.content.reverse(); + setCards(data.content); + setLoading(false); + } + fetch(); + }, [accessToken]); + + const decideColor = (index: number) => { + const remainder = index % 3; + if (remainder == 0) { + return ["from-[#0A06F4] to-[#0A06F4]", "text-white"]; + } else if (remainder == 1) { + return ["from-[#4C49ED] to-[#4C49ED]", "text-white"]; + } else { + return ["from-[#FFF] to-[#FFF]", "text-black"]; + } + }; + const handleAddition = (card: Card1) => { + const newCards = [card, ...cards]; + setCards(newCards); + }; + + if (accessToken == "" && loading == false) { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/creditCards")}` + ); + } + return ( +
+
+ + +
+ {loading ? ( +
+ {Array(3) + .fill(0) + .map((_, index) => ( + + ))} +
+ ) : ( + cards.map((card, index) => { + const [bgColor, textColor] = decideColor(index); + + if (index <= 2) { + return ( + + ); + } + }) + )} +
+
+
+
+ + {loading ? : } +
+
+
+ + setSearchTerm(e.target.value)} + className="p-2 border border-gray-300 rounded-lg basis-4/12 dark:bg-[#313245] dark:border dark:border-[#333B69]" + /> +
+
+ {loading ? ( +
+ {Array(4) + .fill(0) + .map((_, index) => ( + + ))} +
+ ) : filteredCards.length == 0 ? ( +
No Available Cards!
+ ) : ( + filteredCards.map((card, index) => ( + } + handleDetail={handleDialogToogle} + data={[ + ["Card Type", card.cardType], + ["Balance", card.balance.toString()], + ["Card Number", card.semiCardNumber], + ["Card Expiry", convertToDate(card.expiryDate)], + ]} + card={card} + initialValue={dialogOpen} + key={index} + /> + )) + )} +
+
+
+ +
+
+ +
+ +
+
+
+ + +
+
+
+ ); +}; + +export default CreditCards; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/BalanceHistory.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/BalanceHistory.tsx new file mode 100644 index 000000000..3534bd48e --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/BalanceHistory.tsx @@ -0,0 +1,150 @@ +"use client"; +import { TrendingUp } from "lucide-react"; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import Refresh from "@/app/api/auth/[...nextauth]/token/RefreshToken"; +import { getBalanceHistory } from "@/lib/api/transactionController"; + + + +const chartConfig = { + Balance: { + label: "Balance", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +// eslint-disable-next-line react-hooks/rules-of-hooks + + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; +type SessionDataType = { + user: Data; +}; +export function BalanceHistory() { + const [chartData, setChartData] = useState<{ month: string; balance: number }[]>([]); + + const [loading, setLoading] = useState(true); + const [session, setSession] = useState(null); + const router = useRouter(); + const [access_token, setAccess_token] = useState(""); + + + useEffect(() => { + const fetchSession = async () => { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + setLoading(false); + }; + + fetchSession(); + }, [router]); + useEffect(() => { + const fetchBalanceHistory = async () => { + if (!access_token) return; + try { + const balanceHistory = await getBalanceHistory(access_token); + + if (balanceHistory.success) { + const formattedData = balanceHistory.data.map((item) => ({ + month: item.time, // Adjust the time to match your needs (e.g., month) + balance: item.value, + })); + setChartData(formattedData); + } + } catch (error) { + console.error("Error fetching balance history:", error); + } + }; + + fetchBalanceHistory(); + + }, [access_token]); + return ( + + + + Balance History + + + +
+ + + + value.slice(0, 3)} + /> + + } + /> + + + +
+
+
+ ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/CreditCard.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/CreditCard.tsx new file mode 100644 index 000000000..8cc03aee0 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/CreditCard.tsx @@ -0,0 +1,73 @@ +import React from 'react'; + +interface CardProps { + balance: string; + cardHolder: string; + validThru: string; + cardNumber: string; + filterClass?: string; + bgColor?: string; + textColor?: string; + iconBgColor?: string; + showIcon?: boolean; +} + +const CreditCard: React.FC = ({ + balance, + cardHolder, + validThru, + cardNumber, + filterClass = "filter-white", + bgColor = '', + textColor = 'text-white', + iconBgColor = 'bg-opacity-10', + showIcon = true +}) => { + + const isBlueGradient = bgColor.includes('#4C49ED') || bgColor.includes('#0A06F4'); + const ellipseImageSrc = isBlueGradient ? '/group17.svg' : '/group18.svg'; + const iconSrc = isBlueGradient ? '/sim.svg' : '/blackSim.svg'; + + const cardHolderTextColor = isBlueGradient ? 'text-[rgba(255,255,255,0.7)]' : 'text-[#718EBF]'; + + return ( +
+
+
+
+

Balance

+

{balance}

+
+ {showIcon && ( +
+ sim image +
+ )} +
+ +
+
+

CARD HOLDER

+

{cardHolder}

+
+
+

VALID THRU

+

{validThru}

+
+
+ +
+
+
+ {cardNumber} +
+
+ ellipse background +
+
+
+
+ ); +}; + +export default CreditCard; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ExpenseStatistics.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ExpenseStatistics.tsx new file mode 100644 index 000000000..1e888bf9a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ExpenseStatistics.tsx @@ -0,0 +1,193 @@ +"use client"; + +import { TrendingUp } from "lucide-react"; +import { LabelList, Pie, PieChart } from "recharts"; +import { useState, useEffect } from "react"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; +import { + getTransactionIncomes, + getTransactionsExpenses, +} from "@/lib/api/transactionController"; +import Refresh from "@/app/api/auth/[...nextauth]/token/RefreshToken"; +import { IconType } from "react-icons"; + +const initialChartData = [ + { browser: "shopping", amount: 0, fill: "var(--color-shopping)" }, + { browser: "transfer", amount: 0, fill: "var(--color-transfer)" }, + { browser: "deposit", amount: 0, fill: "var(--color-deposit)" }, + {browser: "service", amount: 0, fill: "var(--color-service)"} +]; + +const chartConfig = { + Amount: { + label: "Amount", + }, + shopping: { + label: "Shopping", + color: "hsl(var(--chart-1))", + }, + transfer: { + label: "Transfer", + color: "hsl(var(--chart-2))", + }, + deposit: { + label: "Deposit", + color: "hsl(var(--chart-3))", + }, + service: { + label: "Service", + color: "hsl(var(--chart-4))", + }, +} satisfies ChartConfig; + +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; +}; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; +const ShimmerEffect = () => ( +
+); +export function ExpenseStatistics() { + const [chartData, setChartData] = useState(initialChartData); + const [loading, setLoading] = useState(true); + const [session, setSession] = useState(null); + const router = useRouter(); + const [access_token, setAccess_token] = useState(""); + + + useEffect(() => { + const fetchSession = async () => { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + setLoading(false); + }; + + fetchSession(); + }, [router]); + + + + useEffect(() => { + const fetchAndProcessExpenses = async () => { + try { + if (access_token) { + const { data } = await getTransactionsExpenses(0, 1000, access_token); + console.log("worked", data) + const typeAmounts: { [key: string]: number } = { + shopping: 0, + transfer: 0, + deposit: 0, + service: 0 + }; + + data.content.forEach((transaction: any) => { + if (typeAmounts.hasOwnProperty(transaction.type)) { + typeAmounts[transaction.type] += transaction.amount; + } else { + typeAmounts.other += transaction.amount; // If type is not predefined, add to "other" + } + }); + + setChartData([ + { browser: "shopping", amount: typeAmounts.shopping, fill: "var(--color-shopping)" }, + { browser: "transfer", amount: typeAmounts.transfer, fill: "var(--color-transfer)" }, + { browser: "deposit", amount: typeAmounts.deposit, fill: "var(--color-deposit)" }, + { browser: "service", amount: typeAmounts.service, fill: "var(--color-service)" }, + ]); + + setLoading(false); + } + } catch (error) { + console.error("Error fetching expenses:", error); + setLoading(false); + } + }; + + fetchAndProcessExpenses(); + }); + + return ( + + + + Expense Statistics + + + +
+ {loading ? ( + + ) : ( + + + } + /> + + + chartConfig[value]?.label + } + /> + + + + )} +
+
+
+ ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ImageComponent.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ImageComponent.tsx new file mode 100644 index 000000000..ce2ad15c4 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/ImageComponent.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import Image from 'next/image'; + +interface ImageComponentProps { + src: string; // URL of the image + alt: string; // Alt text for the image + width: number; // Width of the image + height: number; // Height of the image + name: string; // Name to display + role: string; // Role to display +} + +const ImageComponent: React.FC = ({ src, alt, width, height, name, role }) => { + return ( +
+
+
+ {alt} +
+
+

{name}

+

{role}

+
+
+
+ ); +}; + +export default ImageComponent; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/QuickTransfer.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/QuickTransfer.tsx new file mode 100644 index 000000000..20e70e989 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/QuickTransfer.tsx @@ -0,0 +1,233 @@ +/* eslint-disable react/no-children-prop */ +import Image from "next/image"; +import ImageComponent from "../components/ImageComponent" +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { getSession } from 'next-auth/react'; +import Refresh from "../../api/auth/[...nextauth]/token/RefreshToken"; +import { getQuickTransfers } from "@/lib/api/transactionController"; +import { PropsWithChildren } from 'react'; +import { PostTransactionRequest } from "@/types/transactionController.interface"; +import { postTransaction } from "@/lib/api/transactionController"; + + +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel" + +type infoType = { + id: string; + name: string; + username: string; + city: string; + country: string; + profilePicture: string; +} +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; +export default function Home() { + const ShimmerEffect = () => ( +
+

+ + {/* Carousel Shimmer */} +
+ + + {[...Array(3)].map((_, index) => ( + +
+
+ ))} +
+
+
+ +
+

+
+
+ +
+ ); + + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [transfer, setQuickTransfer] = useState([]); + const [activeIndex, setActiveIndex] = useState(-1); + const [amount, setAmount] = useState(""); + const [showDialog, setShowDialog] = useState(false); + + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push(`./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}`); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, [router]); + + useEffect(() => { + const addingData = async () => { + if (!access_token) return; + if (access_token) { + const transfers = await getQuickTransfers(100, access_token); + console.log("Fetching Completeddddd", transfers); + + setQuickTransfer(transfers.data); // Set the content array + } + }; + addingData(); + }), []; + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + if (activeIndex < 0 || !transfer[activeIndex]) { + alert("You have to choose a user and Enter data.") + console.error('Invalid index or no transfer data available'); + return; + } + else{ + console.log(amount) + } + + const transactionDetails: PostTransactionRequest = { + type: 'transfer', // Or dynamically set based on your application logic + description: `Transfer to ${transfer[activeIndex].username}`, + amount: parseInt(amount), + receiverUserName: transfer[activeIndex].username, + }; + + try { + console.log(transactionDetails); + const response = await postTransaction(transactionDetails, access_token); + if (response.success === true){ + // setShowDialog(true); // Show the dialog on success + console.log() + alert("success") + console.log('Transaction successful:', response); + } + else{ + alert('NO'); // Display success message + console.log(response.message) + } + } catch (error) { + console.error('Error posting transaction:', error); + } + }; + + const newLocal = "M1 1L7.5 7.5L1 14"; + return ( +
+ { + loading ? ( + + ): ( +
+

Quick Transfers

+
+ {/* Image Component */} +
+ + + {transfer.map((item,index) => ( + {setActiveIndex(index)}}> + + + ))} + + + + + + + +
+ +
+

Write Amount

+
+
+
+
+ setAmount(e.target.value)} + /> + +
+
+
+ +
+
+
+
+ ) + } + +
+ + ); + +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/RecentTransaction.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/RecentTransaction.tsx new file mode 100644 index 000000000..b0d3b1731 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/RecentTransaction.tsx @@ -0,0 +1,196 @@ +import React, { useEffect, useState } from 'react' +import { + getTransactionIncomes, + getTransactions, + getTransactionsExpenses, +} from "@/lib/api/transactionController"; +import { getSession } from "next-auth/react"; +import Refresh from "../../api/auth/[...nextauth]/token/RefreshToken"; + +import { TransactionData, TransactionResponse } from "@/types/transactionController.interface"; +import { IconType } from 'react-icons'; +import { useRouter } from "next/navigation"; + +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; +}; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; +const RecentTransaction = () => { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [balance, setBalance] = useState("Loading..."); + const [income, setIncome] = useState("Loading..."); + const [expense, setExpense] = useState("Loading..."); + const [transaction, setTransaction] = useState([]) + const ShimmerEffect = () => { + return ( +
+ {[...Array(3)].map((_, index) => ( +
+
+
+
+
+
+ +
+ ))} +
+ ); + }; + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, [router]); + useEffect(() => { + // Simulate a data fetch with a delay + setTimeout(() => { + setTransaction([]); // Simulate empty transaction data + }, 2000); + }, []); + // Combined fetching data to reduce multiple useEffect hooks + useEffect(() => { + const fetchData = async () => { + if (!access_token) return; + + try { + // Fetch Cards + + // Fetch Transactions + const transactionData:TransactionResponse = await getTransactions(0, 3, access_token) + setTransaction(transactionData.data.content) + + } catch (error) { + console.error("Error fetching data:", error); + } + }; + + fetchData(); + }, [access_token]); + return ( +
+ +
+
+

Recent Transaction

+
+ + { + transaction.length > 0 ? ( + transaction.slice(0,3).map((txn, index) => ( +
+
+
+
+ {txn.type === "shopping" && + + + + + + } + {txn.type === "transfer" && + + + + + + } + {txn.type === "deposit" && + + + + + + + } + {txn.type === "withdrawal" && + + + + + + + + } + + + +
+
+
+

+ {txn.description} +

+

+ {formatDate(txn.date)} +

+
+
+
+

+ {txn.amount < 0 ? `- ${Math.abs(txn.amount)}` : `+ ${txn.amount}`} +

+ +
+
+ )) + ):( + + ) + } + +
+
+
+
+ + ) +} + +export default RecentTransaction +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "short", + day: "numeric", + }; + + return date.toLocaleDateString("en-US", options); +}; \ No newline at end of file diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/WeeklyActivity.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/WeeklyActivity.tsx new file mode 100644 index 000000000..b2f49cadc --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/components/WeeklyActivity.tsx @@ -0,0 +1,94 @@ +"use client"; + +import { TrendingUp } from "lucide-react"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/components/ui/chart"; + +const chartData = [ + { month: "Saturday", desktop: 186, mobile: 80 }, + { month: "Sunday", desktop: 305, mobile: 200 }, + { month: "Monday", desktop: 237, mobile: 120 }, + { month: "Tuesday", desktop: 73, mobile: 190 }, + { month: "Wednesday", desktop: 209, mobile: 130 }, + { month: "Thursday", desktop: 214, mobile: 140 }, + { month: "Friday", desktop: 214, mobile: 140 }, +]; + +const chartConfig = { + desktop: { + label: "Desktop", + color: "#1814f3", // Color for desktop bars + }, + mobile: { + label: "Mobile", + color: "#16dbcc", // Color for mobile bars + }, +} satisfies ChartConfig; + +export function WeeklyActivity() { + return ( + + + + Weekly Activity + + + + + + + value} + domain={[0, 500]} + interval={0} + tickLine={false} + axisLine={false} + tickMargin={10} + /> + value.slice(0, 3)} + /> + } + /> + + + + + + + ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/page.tsx new file mode 100644 index 000000000..7059d2357 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/dashboard/page.tsx @@ -0,0 +1,270 @@ +'use client' +import Image from "next/image"; +import { IconType } from "react-icons"; +import { useEffect, useState } from "react"; +import ImageComponent from "./components/ImageComponent"; +import Reviving from "./components/QuickTransfer"; +import { BalanceHistory } from "./components/BalanceHistory"; +import { WeeklyActivity } from "./components/WeeklyActivity"; +import { ExpenseStatistics } from "./components/ExpenseStatistics"; +import RecentTransaction from "./components/RecentTransaction"; +import CreditCard from "./components/CreditCard"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { getCards } from "@/lib/api/cardController"; +import { GetCardsResponse, Card as CardType } from "@/types/cardController.Interface"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import { TransactionData, TransactionResponse } from "@/types/transactionController.interface"; + +import { + getTransactionIncomes, + getTransactions, + getTransactionsExpenses, +} from "@/lib/api/transactionController"; +// import {RecentTransaction} from "@/components/RecentTransaction" +import { +MdHome, +MdSettings, +MdAttachMoney, +MdAccountBalance, +} from "react-icons/md"; + +type DataItem = { + heading: string; + text: string; + headingStyle: string; + dataStyle: string; +}; + +type Column = { + icon: IconType; + iconStyle: string; + data: DataItem[]; +}; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; +type AllowedProperties = { + ListCardLoading: () => JSX.Element; + // Add other allowed properties here +}; +type SessionDataType = { + user: Data; +}; +// export const ListCardLoading = () => { +// return ( +//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+// ); +// }; +export default function Home() { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [getCard, setGetCards] = useState(); + const [transaction, setTransaction] = useState([]) + + const createTransactionColumn = (transaction: TransactionData): Column => { + return { + icon: MdAccountBalance, // Default icon, you can customize based on type + iconStyle: "text-[#16DBCC] bg-[#DCFAF8]", // Default iconStyle, you can customize based on type + data: [ + { + heading: transaction.description, + text: formatDate(transaction.date), + headingStyle: "text-sm font-bold text-nowrap", + dataStyle: "text-xs text-nowrap text-[#718EBF]", + }, + { + heading: transaction.amount < 0 ? `-${Math.abs(transaction.amount)}` : `+${transaction.amount}`, + text: transaction.receiverUserName || "unknown source", + headingStyle: `text-xs font-bold ${transaction.amount < 0 ? "text-[#FE5C73]" : "text-[#16DBAA]"}`, + dataStyle: "text-xs text-nowrap", + }, + ], + }; + }; + // getting the session ends here + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, [router]); + + // Fetching cards + useEffect(() => { + const addingData = async () => { + if (!access_token) return; + if (access_token) { + const cardData = await getCards(access_token, 0, 3); + console.log("Fetching Complete", cardData.content) + setGetCards(cardData.content); + + // Fetch Transactions + const transactionData:TransactionResponse = await getTransactions(0, 3, access_token) + setTransaction(transactionData.data.content) + } + }; + addingData(); + }); + + if (loading) return null; // Don't render anything while loading + + + if (!session) { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + return null; + } + return ( +
+ +
+ {/* Mobile Version */} +
+
+

My Cards

+

See All

+
+
+ +
+ +
+ fad +
+ {getCard && getCard.length > 0 && ( + + )} +
+
+
+
+ + + + + +
+ + {/* Web Version */} +
+ {/*
+

My Cards

+

See All

+
*/} +
+
+ {/* My Cards Section */} +
+

My Cards

+

See All

+
+
+ + {loading &&

Loading ...

} + + {!loading && getCard && + getCard.map((items, index) => ( + + ))} +
+
+ +
+

Recent Transaction

+ +
+
+ +
+
+

Weekly Activity

+ +
+
+

Expense Statistics

+ +
+
+
+
+

Quick Transfers

+ +
+
+

Balance History

+ +
+
+
+
+
+ + + ); + +} +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "short", + day: "numeric", + }; + + return date.toLocaleDateString("en-US", options); +}; \ No newline at end of file diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/firebase.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/firebase.ts new file mode 100644 index 000000000..b453add9a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/firebase.ts @@ -0,0 +1,16 @@ +// Import the functions you need from the SDKs you need +import { initializeApp } from "firebase/app"; +import { getStorage } from "firebase/storage"; +const firebaseConfig = { + apiKey: "AIzaSyCv_JSIXqdk05YebMCWuDm79VUlWhvuOgA", + authDomain: "a2sv-wallet.firebaseapp.com", + projectId: "a2sv-wallet", + storageBucket: "a2sv-wallet.appspot.com", + messagingSenderId: "136604332771", + appId: "1:136604332771:web:8264100cc84c484fbc4bd6", + measurementId: "G-44PK3KTJJT" +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); +export const storage = getStorage(app) \ No newline at end of file diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/globals.css b/web/AASTU-web-group-2/a2sv-banking-system/app/globals.css new file mode 100644 index 000000000..fa5f09580 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/globals.css @@ -0,0 +1,78 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } + + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + +.react-datepicker-wrapper { + @apply inline-block p-0 border-0 w-full; +} +@layer base { + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/investments/back/Invest.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/back/Invest.ts new file mode 100644 index 000000000..1057adbc2 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/back/Invest.ts @@ -0,0 +1,23 @@ +import axios from "axios"; +const baseUrl = "https://a2svwallets.onrender.com"; +export async function getRandomInvestementData( + months: number, + years: number, + accessToken: string +) { + try { + const response = await axios.get( + baseUrl + `/user/random-investment-data?months=${months}&years=${years}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); + return response.data.data; + } catch (error) { + console.error("Error fetching random investment data:", error); + throw error; + } +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Card1.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Card1.tsx new file mode 100644 index 000000000..2f8055650 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Card1.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import Image from "next/image"; +interface Card1Props { + text: string; + num: number | string; + img: string; +} +const Card1 = ({ text, num, img }: Card1Props) => { + return ( +
+
+
+
+ image +
+
+
+ {text} +
+
+ {num} +
+
+
+
+
+ ); +}; + +export default Card1; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/LineChart.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/LineChart.tsx new file mode 100644 index 000000000..d4b2712c0 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/LineChart.tsx @@ -0,0 +1,179 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import Refresh from "../../api/auth/[...nextauth]/token/RefreshToken"; +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; +import { getRandomInvestementData } from "../back/Invest"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/app/loans/components/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/app/loans/components/chart"; +interface arr { + time: string; + value: number; +} +const token = + "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJsc2FqZGxzanNuIiwiaWF0IjoxNzI0MTU1NzkzLCJleHAiOjE3MjQyNDIxOTN9.wi7oRgF81zMp1v8tPzRPmAj4GOLaYy4bV_TMVvtWmzg2mjrTThiruT_Fswcyu1eq"; + +interface info { + totalInvestment: number; + rateOfReturn: number; + yearlyTotalInvestment: arr[]; + monthlyRevenue: arr[]; +} +const chartConfig = { + value: { + label: "value", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +export default function Linechart() { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setloading] = useState(true); + const [Loading, setLoading] = useState(true); + const [data, setdata] = useState({ + totalInvestment: 1, + rateOfReturn: 1, + yearlyTotalInvestment: [], + monthlyRevenue: [], + }); + + // Getting the session from the server and Access Token From Refresh + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setloading(false); + } + }; + + fetchSession(); + }, [router]); + + // Combined fetching data to reduce multiple useEffect hooks + useEffect(() => { + const fetchData = async () => { + if (!access_token) return; + + try { + // Fetch data + const d: info = await getRandomInvestementData(11, 20, access_token); + setdata(d); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [access_token]); + + if (loading || Loading) + return ( +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ ); + const { yearlyTotalInvestment } = data; + return ( + + + + + + value.slice(0, 4)} + /> + + } + /> + + + + + + ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Monthly.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Monthly.tsx new file mode 100644 index 000000000..0a2f3fa1a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/components/Monthly.tsx @@ -0,0 +1,187 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import Refresh from "../../api/auth/[...nextauth]/token/RefreshToken"; +import Card1 from "../components/Card1"; +import { getServerSession } from "next-auth"; +import { options } from "../../api/auth/[...nextauth]/options"; +import { TrendingUp } from "lucide-react"; +import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; +import { getRandomInvestementData } from "../back/Invest"; + +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/app/loans/components/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "@/app/loans/components/chart"; +import { cp } from "fs"; +const chartConfig = { + value: { + label: "value", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; +interface arr { + time: string; + value: number; +} +const token = + "eyJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJsc2FqZGxzanNuIiwiaWF0IjoxNzI0MTU1NzkzLCJleHAiOjE3MjQyNDIxOTN9.wi7oRgF81zMp1v8tPzRPmAj4GOLaYy4bV_TMVvtWmzg2mjrTThiruT_Fswcyu1eq"; + +interface info { + totalInvestment: number; + rateOfReturn: number; + yearlyTotalInvestment: arr[]; + monthlyRevenue: arr[]; +} + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +export default function Monthly() { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setloading] = useState(true); + const [Loading, setLoading] = useState(true); + const [data, setdata] = useState({ + totalInvestment: 1, + rateOfReturn: 1, + yearlyTotalInvestment: [], + monthlyRevenue: [], + }); + + // Getting the session from the server and Access Token From Refresh + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setloading(false); + } + }; + + fetchSession(); + }, [router]); + + // Combined fetching data to reduce multiple useEffect hooks + useEffect(() => { + const fetchData = async () => { + if (!access_token) return; + + try { + // Fetch data + const d: info = await getRandomInvestementData(11, 2021, access_token); + setdata(d); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [access_token]); + + if (loading || Loading) + return ( +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ ); + // console.log(data); + const { monthlyRevenue } = data; + return ( + + + + + + value.slice(0, 7)} + /> + value.slice(0, 3)} + /> + } + /> + + + + + + ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/investments/page.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/page.tsx new file mode 100644 index 000000000..bb64d5c0a --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/investments/page.tsx @@ -0,0 +1,400 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { getSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import Refresh from "../api/auth/[...nextauth]/token/RefreshToken"; +import Card1 from "./components/Card1"; +import Linechart from "./components/LineChart"; +import Monthly from "./components/Monthly"; +import { getServerSession } from "next-auth"; +import { options } from "../api/auth/[...nextauth]/options"; +import { getRandomInvestementData } from "./back/Invest"; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableFooter, + TableRow, +} from "@/app/loans/components/table"; + +const invoices = [ + { + slno: "01.", + name: "Trivago", + price: "$520", + return: "+5%", + }, + { + slno: "02.", + name: "Canon", + price: "$480", + return: "+10%", + }, + { + slno: "03.", + name: "Uber Food", + price: "$350", + return: "-3%", + }, + { + slno: "04.", + name: "Nokia", + price: "$40,500", + return: "+2%", + }, + { + slno: "05.", + name: "Tiktok", + price: "$670", + return: "-12%", + }, +]; +interface arr { + time: string; + value: number; +} +const loanid = "66c3054e80b7cf4a6c2f7709"; + +type ISODateString = string; + +interface DefaultSession { + user?: { + name?: string | null; + email?: string | null; + image?: string | null; + access_token?: string | any; + }; + expires: ISODateString; +} +interface datatype { + totalInvestment: any; + rateOfReturn: any; + yearlyTotalInvestment: [{ time: string; value: number }]; + monthlyRevenue: arr[]; +} + +type Data = { + access_token: string; + data: string; + refresh_token: string; +}; + +type SessionDataType = { + user: Data; +}; + +export default function Home() { + const [session, setSession] = useState(null); + const [access_token, setAccess_token] = useState(""); + const router = useRouter(); + const [loading, setloading] = useState(true); + const [Loading, setLoading] = useState(true); + const [data, setdata] = useState(); + + // Getting the session from the server and Access Token From Refresh + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = (await getSession()) as SessionDataType | null; + setAccess_token(await Refresh()); + if (sessionData && sessionData.user) { + setSession(sessionData.user); + } else { + router.push( + `./api/auth/signin?callbackUrl=${encodeURIComponent("/accounts")}` + ); + } + } catch (error) { + console.error("Error fetching session:", error); + } finally { + setloading(false); + } + }; + + fetchSession(); + }, [router]); + + // Combined fetching data to reduce multiple useEffect hooks + useEffect(() => { + const fetchData = async () => { + if (!access_token) return; + + try { + // Fetch data + const d: datatype = await getRandomInvestementData( + 11, + 2021, + access_token + ); + setdata(d); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [access_token]); + + if (loading || Loading) + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); + + return ( +
+
+ + + +
+
+
+
+ Yearly Total Investment +
+ +
+
+
+ Monthly Revenue +
+
+ +
+
+
+
+
+
+ My Investment +
+
+
+
+ + + + +
+
+
+ Apple Store +
+
+ E-commerce, Marketplace +
+
+
+
+ $54,000 +
+
+ Envestment Value +
+
+
+
+ +16% +
+
+ Return Value +
+
+
+
+
+ + + + + + + +
+
+
+ Samsung Mobile +
+
+ E-commerce, Marketplace +
+
+
+
+ $25,300 +
+
+ Envestment Value +
+
+
+
+ -4% +
+
+ Return Value +
+
+
+
+
+ + + + + +
+
+
+ Tesla Motors +
+
+ E-commerce, Marketplace +
+
+
+
+ $8,200 +
+
+ Envestment Value +
+
+
+
+ +25% +
+
+ Return Value +
+
+
+
+
+
+
+ Trending Stock +
+ + + + + SL No + + + Name + + + Price + + + Return + + + + + {invoices.map((invoice) => ( + + + {invoice.slno} + + + {invoice.name} + + + {invoice.price} + + + {invoice.return} + + + ))} + +
+
+
+
+ ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/layout.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/layout.tsx new file mode 100644 index 000000000..c875a4695 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Inter, Lato } from "next/font/google"; +import "./globals.css"; +import Navigation from "./components/Navigation"; +import { DarkModeProvider } from "./components/Context/DarkModeContext"; +import ProgressBar from "./components/loadingprovider/ProgressBar"; +const inter = Inter({ subsets: ["latin"] }); +const lato = Lato({ + subsets: ["latin"], + weight: ["400", "700"], +}); + +export const metadata: Metadata = { + title: "A2SV Wallet", + description: "Built for a2sv", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + {children} + + + + + ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/ActiveLoanController.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/ActiveLoanController.ts new file mode 100644 index 000000000..f9c065e0d --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/ActiveLoanController.ts @@ -0,0 +1,108 @@ +import axios from "axios"; +const baseUrl = "https://a2svwallets.onrender.com"; +const activeloansall = async (token: string, size: number, page: number) => { + const response = await axios.get( + baseUrl + `/active-loans/all?page=${page}&size=${size}`, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + return response.data.data; +}; + +const activeloansdetaildata = async (token: string) => { + const response = await axios.get(baseUrl + "/active-loans/detail-data", { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + return response.data.data; +}; + +const activeloansmyloans = async (token: string) => { + const response = await axios.get(baseUrl + "/active-loans/my-loans", { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + return response.data.data; +}; + +const activeloansid = async (loanid: string, token: string) => { + const response = await axios.get(baseUrl + `/active-loans/${loanid}`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + return response.data.data; +}; + +const activeloansidapprove = async (loanid: string, token: string) => { + const respons7 = await axios.post( + baseUrl + `/active-loans/${loanid}/approve`, + {}, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); +}; + +const activeloansidreject = async (loanid: string, token: string) => { + const response = await axios.post( + baseUrl + `/active-loans/${loanid}/reject`, + {}, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + return response.data; +}; + +interface activeloanbody { + loanAmount: number; + duration: number; + interestRate: number; + type: string; +} + +type Form = { + loanAmount: number; + duration: number; + interestRate: number; + type: string; +}; + +const activeloans = async (token: string, data: Form) => { + const response = await axios.post( + baseUrl + "/active-loans", + JSON.stringify(data), + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + return response.data.data; +}; + +export { + activeloansall, + activeloansdetaildata, + activeloansmyloans, + activeloansid, + activeloansidapprove, + activeloansidreject, + activeloans, +}; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/Invest.ts b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/Invest.ts new file mode 100644 index 000000000..1057adbc2 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/back/Invest.ts @@ -0,0 +1,23 @@ +import axios from "axios"; +const baseUrl = "https://a2svwallets.onrender.com"; +export async function getRandomInvestementData( + months: number, + years: number, + accessToken: string +) { + try { + const response = await axios.get( + baseUrl + `/user/random-investment-data?months=${months}&years=${years}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + } + ); + return response.data.data; + } catch (error) { + console.error("Error fetching random investment data:", error); + throw error; + } +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Card1.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Card1.tsx new file mode 100644 index 000000000..395b1449d --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Card1.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import Image from "next/image"; +interface Card1Props { + + text: string; + num: number | string; + img: string; +} +const Card1 = ({text, num, img}: Card1Props) => { + return ( +
+
+
+
+ image +
+
+
+ {text} +
+
+ {num} +
+
+
+
+
+ ); +}; + +export default Card1; diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Createloan.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Createloan.tsx new file mode 100644 index 000000000..601c739c8 --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/Createloan.tsx @@ -0,0 +1,132 @@ +import { useForm } from "react-hook-form"; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import Card1 from "./Card1"; +type Form = { + loanAmount: number; + duration: number; + interestRate: number; + type: string +} + +export function SheetDemo({ handleform }: { handleform : (data:Form)=>void}) { + const forms = useForm
(); + const { register, control, handleSubmit, formState, reset } = forms; + const { errors } = formState; + const onSubmit = (data: Form) => { + handleform(data); + console.log("formsubmited", data); + reset(); + }; + return ( + + + + + + + Loan Money + + You can choose any type of your choice. + + + + + {" "} +

{errors.loanAmount?.message}

+
+ +
+ +

{errors.duration?.message}

+
+ +
+ +

{errors.interestRate?.message}

+
+ +
+ +
+

{errors.type?.message}

+
+ +
+ +
+
+ ); +} diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/card.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/card.tsx new file mode 100644 index 000000000..afa13ecfa --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/card.tsx @@ -0,0 +1,79 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/chart.tsx b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/chart.tsx new file mode 100644 index 000000000..8620baa3b --- /dev/null +++ b/web/AASTU-web-group-2/a2sv-banking-system/app/loans/components/chart.tsx @@ -0,0 +1,365 @@ +"use client" + +import * as React from "react" +import * as RechartsPrimitive from "recharts" + +import { cn } from "@/lib/utils" + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode + icon?: React.ComponentType + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ) +} + +type ChartContextProps = { + config: ChartConfig +} + +const ChartContext = React.createContext(null) + +function useChart() { + const context = React.useContext(ChartContext) + + if (!context) { + throw new Error("useChart must be used within a ") + } + + return context +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"] + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId() + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` + + return ( + +
+ + + {children} + +
+
+ ) +}) +ChartContainer.displayName = "Chart" + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color + ) + + if (!colorConfig.length) { + return null + } + + return ( +