aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components
diff options
context:
space:
mode:
authorLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-25 01:09:30 +0530
committerLibravatarLibravatar Biswa Kalyan Bhuyan <biswa@surgot.in> 2025-04-25 01:09:30 +0530
commit8733795c8449f3514369d7b4220934760e386f1b (patch)
tree86b70bd897f22646df401f5e24e28bb35f3ca155 /frontend/src/components
parent4cd8498ad30e6ea5f01e81346a81a2a1134864be (diff)
downloadfinance-8733795c8449f3514369d7b4220934760e386f1b.tar.gz
finance-8733795c8449f3514369d7b4220934760e386f1b.tar.bz2
finance-8733795c8449f3514369d7b4220934760e386f1b.zip
finance/frontend: fix: did some minor changes to the frontend
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/shared/AuthContext.tsx92
-rw-r--r--frontend/src/components/shared/Notification.tsx58
-rw-r--r--frontend/src/components/shared/ProtectedRoute.tsx37
3 files changed, 163 insertions, 24 deletions
diff --git a/frontend/src/components/shared/AuthContext.tsx b/frontend/src/components/shared/AuthContext.tsx
new file mode 100644
index 0000000..a3ec5a0
--- /dev/null
+++ b/frontend/src/components/shared/AuthContext.tsx
@@ -0,0 +1,92 @@
+'use client';
+
+import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
+import { useRouter } from 'next/navigation';
+import { authApi, userApi } from '@/lib/api';
+
+interface User {
+ ID: number;
+ Name: string;
+ Email: string;
+}
+
+interface AuthContextType {
+ user: User | null;
+ isLoading: boolean;
+ isAuthenticated: boolean;
+ login: (email: string, password: string) => Promise<void>;
+ signup: (name: string, email: string, password: string) => Promise<void>;
+ logout: () => void;
+}
+
+const AuthContext = createContext<AuthContextType | null>(null);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState<User | null>(null);
+ const [isLoading, setIsLoading] = useState<boolean>(true);
+ const router = useRouter();
+
+ // Check if the user is already logged in
+ useEffect(() => {
+ const checkAuth = async () => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ setIsLoading(false);
+ return;
+ }
+
+ try {
+ const userData = await userApi.getProfile();
+ setUser(userData);
+ } catch (error) {
+ // Clear invalid token
+ localStorage.removeItem('token');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ checkAuth();
+ }, []);
+
+ // Login function
+ const login = async (email: string, password: string) => {
+ const response = await authApi.login(email, password);
+ setUser(response.user);
+ return response;
+ };
+
+ // Signup function
+ const signup = async (name: string, email: string, password: string) => {
+ return await authApi.signup(name, email, password);
+ };
+
+ // Logout function
+ const logout = () => {
+ authApi.logout();
+ setUser(null);
+ };
+
+ return (
+ <AuthContext.Provider
+ value={{
+ user,
+ isLoading,
+ isAuthenticated: !!user,
+ login,
+ signup,
+ logout
+ }}
+ >
+ {children}
+ </AuthContext.Provider>
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+} \ No newline at end of file
diff --git a/frontend/src/components/shared/Notification.tsx b/frontend/src/components/shared/Notification.tsx
index bcc11c4..68cbc7a 100644
--- a/frontend/src/components/shared/Notification.tsx
+++ b/frontend/src/components/shared/Notification.tsx
@@ -1,11 +1,29 @@
'use client';
import { useEffect, useState } from 'react';
-import { CheckCircle, AlertCircle, X } from 'lucide-react';
+import { CheckCircle, AlertCircle, Info, X } from 'lucide-react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { cn } from '@/lib/utils';
export type NotificationType = 'success' | 'error' | 'info';
-interface NotificationProps {
+const notificationVariants = cva(
+ "fixed z-50 top-4 right-4 flex items-center gap-3 p-4 rounded-lg shadow-lg border max-w-sm transition-all duration-300 animate-in fade-in slide-in-from-top-5",
+ {
+ variants: {
+ variant: {
+ success: "bg-background border-border text-foreground",
+ error: "bg-background border-border text-foreground",
+ info: "bg-background border-border text-foreground",
+ }
+ },
+ defaultVariants: {
+ variant: "info",
+ },
+ }
+);
+
+interface NotificationProps extends VariantProps<typeof notificationVariants> {
type: NotificationType;
message: string;
duration?: number;
@@ -41,38 +59,30 @@ export function Notification({
const getIcon = () => {
switch (type) {
case 'success':
- return <CheckCircle className="h-5 w-5 text-green-500" />;
+ return <CheckCircle className="h-5 w-5 text-primary" />;
case 'error':
- return <AlertCircle className="h-5 w-5 text-red-500" />;
+ return <AlertCircle className="h-5 w-5 text-destructive" />;
case 'info':
- return <AlertCircle className="h-5 w-5 text-blue-500" />;
+ return <Info className="h-5 w-5 text-primary" />;
default:
return null;
}
};
- const getContainerClasses = () => {
- const baseClasses = 'fixed top-4 right-4 z-50 flex items-center gap-3 rounded-lg p-4 shadow-md max-w-sm';
-
- switch (type) {
- case 'success':
- return `${baseClasses} bg-green-50 text-green-800 border border-green-200`;
- case 'error':
- return `${baseClasses} bg-red-50 text-red-800 border border-red-200`;
- case 'info':
- return `${baseClasses} bg-blue-50 text-blue-800 border border-blue-200`;
- default:
- return baseClasses;
- }
- };
-
return (
- <div className={getContainerClasses()}>
- {getIcon()}
- <div className="flex-1">{message}</div>
+ <div className={cn(notificationVariants({ variant: type as any }))}>
+ <div className={cn(
+ "flex h-8 w-8 items-center justify-center rounded-full",
+ type === 'success' && "bg-primary/10",
+ type === 'error' && "bg-destructive/10",
+ type === 'info' && "bg-primary/10"
+ )}>
+ {getIcon()}
+ </div>
+ <div className="flex-1 text-sm font-medium">{message}</div>
<button
onClick={handleClose}
- className="text-gray-500 hover:text-gray-700 focus:outline-none"
+ className="rounded-full p-1 text-muted-foreground hover:bg-muted hover:text-foreground focus:outline-none transition-colors"
aria-label="Close notification"
>
<X className="h-4 w-4" />
diff --git a/frontend/src/components/shared/ProtectedRoute.tsx b/frontend/src/components/shared/ProtectedRoute.tsx
new file mode 100644
index 0000000..f5ecede
--- /dev/null
+++ b/frontend/src/components/shared/ProtectedRoute.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import { useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+import { useAuth } from './AuthContext';
+
+interface ProtectedRouteProps {
+ children: React.ReactNode;
+}
+
+export default function ProtectedRoute({ children }: ProtectedRouteProps) {
+ const { isAuthenticated, isLoading } = useAuth();
+ const router = useRouter();
+
+ useEffect(() => {
+ if (!isLoading && !isAuthenticated) {
+ router.push('/login');
+ }
+ }, [isAuthenticated, isLoading, router]);
+
+ // Show nothing while checking authentication
+ if (isLoading) {
+ return (
+ <div className="flex items-center justify-center h-screen">
+ <div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
+ </div>
+ );
+ }
+
+ // If not authenticated, don't render children (will redirect in useEffect)
+ if (!isAuthenticated) {
+ return null;
+ }
+
+ // If authenticated, render children
+ return <>{children}</>;
+} \ No newline at end of file