diff --git a/src/App.tsx b/src/App.tsx index fbe0220..b98f632 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,13 +23,14 @@ import { api } from './services/api'; import DefaultLayout from './layouts/DefaultLayout'; -import Dashboard from './pages/Dashboard'; +// import Dashboard from './pages/Dashboard'; import Identifiers from './pages/Identifiers'; import Permissions from './pages/Permissions'; import Attestation from './pages/Identifiers/Attestation'; import Callback from './pages/Callback'; import ProtectedRoute from './ProtectedRoute'; import { LitProvider } from './hooks/LitProvider'; +import { CustomSnackbar } from './components/shared/CustomSnackbar'; const queryClient = new QueryClient({ defaultOptions: { @@ -142,7 +143,10 @@ const App: React.FC = () => { } > - } /> + } + /> } /> { } /> Not found} /> + diff --git a/src/ProtectedRoute.tsx b/src/ProtectedRoute.tsx index 732e684..79d2b9f 100644 --- a/src/ProtectedRoute.tsx +++ b/src/ProtectedRoute.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { Navigate } from 'react-router-dom'; import CircularProgress from '@mui/material/CircularProgress'; import Backdrop from '@mui/material/Backdrop'; +import { useAccount } from 'wagmi'; interface ProtectedRouteProps { children: JSX.Element; @@ -11,6 +12,15 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => { const [loading, setLoading] = useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false); + const { isConnected } = useAccount(); + + useEffect(() => { + if (!isConnected) { + setIsAuthenticated(false); + localStorage.removeItem('OCI_TOKEN'); + } + }, [isConnected]); + useEffect(() => { const checkAuthStatus = async () => { const token = localStorage.getItem('OCI_TOKEN'); diff --git a/src/components/shared/CustomSnackbar.tsx b/src/components/shared/CustomSnackbar.tsx new file mode 100644 index 0000000..800aea6 --- /dev/null +++ b/src/components/shared/CustomSnackbar.tsx @@ -0,0 +1,33 @@ +import { Snackbar, Alert } from '@mui/material'; + +import useSnackbarStore from '../../store/useSnackbarStore'; + +/** + * CustomSnackbar component displays a Snackbar using Material-UI's Snackbar component. + * It uses Zustand store for managing Snackbar state globally. + * + * @returns {JSX.Element} The rendered CustomSnackbar component. + */ +export const CustomSnackbar = (): JSX.Element => { + const { message, open, options, closeSnackbar } = useSnackbarStore(); + + return ( + + + {message} + + + ); +}; diff --git a/src/libs/constants.ts b/src/libs/constants.ts index 4938b27..ece2e56 100644 --- a/src/libs/constants.ts +++ b/src/libs/constants.ts @@ -1,4 +1,4 @@ -import SpaceDashboardIcon from '@mui/icons-material/SpaceDashboard'; +// import SpaceDashboardIcon from '@mui/icons-material/SpaceDashboard'; import FingerprintIcon from '@mui/icons-material/Fingerprint'; import { SiAdguard } from 'react-icons/si'; import { SvgIconComponent } from '@mui/icons-material'; @@ -13,11 +13,11 @@ export interface MenuItem { export const DRAWER_WIDTH = 240; export const SIDEBAR_MENU: MenuItem[] = [ - { - title: 'Dashboard', - path: '/', - icon: SpaceDashboardIcon, - }, + // { + // title: 'Dashboard', + // path: '/', + // icon: SpaceDashboardIcon, + // }, { title: 'Identifiers', path: '/identifiers', diff --git a/src/pages/Identifiers/Attestation/Attestation.tsx b/src/pages/Identifiers/Attestation/Attestation.tsx index b16b887..baec5b0 100644 --- a/src/pages/Identifiers/Attestation/Attestation.tsx +++ b/src/pages/Identifiers/Attestation/Attestation.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useEffect } from 'react'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; @@ -21,6 +22,7 @@ import { convertStringsToBigInts, getTokenForProvider, } from '../../../utils/helper'; +import useSnackbarStore from '../../../store/useSnackbarStore'; const steps = [{ label: 'Auth' }, { label: 'Attest' }, { label: 'Transact' }]; @@ -31,7 +33,7 @@ type DecodedToken = { provider: Provider; iat: number; exp: number }; export function Attestation() { const { isConnected, address } = useAccount(); const signer = useSigner(); - + const { showSnackbar } = useSnackbarStore(); const { providers } = useParams<{ providers: 'DISCORD' | 'GOOGLE' }>(); const location = useLocation(); const navigate = useNavigate(); @@ -149,9 +151,26 @@ export function Attestation() { const newAttestationUID = await tx.wait(); + showSnackbar('Attestation created successfully', { + severity: 'success', + }); + + navigate('/identifiers'); + console.log('New attestation UID:', newAttestationUID); console.log('Transaction receipt:', tx.receipt); + } catch (error: any) { + const errorCode = error?.info?.error?.code || ''; + + if (errorCode === 4001) { + showSnackbar( + `${errorCode}, you reject the transaction. please try again...`, + { + severity: 'error', + } + ); + } } finally { setIsAttesting(false); } diff --git a/src/pages/Identifiers/Identifiers.tsx b/src/pages/Identifiers/Identifiers.tsx index 7a3fe3d..2595131 100644 --- a/src/pages/Identifiers/Identifiers.tsx +++ b/src/pages/Identifiers/Identifiers.tsx @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState, useCallback } from 'react'; import { List, @@ -12,6 +14,8 @@ import { Avatar, CircularProgress, IconButton, + Backdrop, + Stack, } from '@mui/material'; import VerifiedIcon from '@mui/icons-material/Verified'; import VisibilityIcon from '@mui/icons-material/Visibility'; @@ -35,6 +39,7 @@ import { } from '../../services/api/eas/query'; import { RevokePayload } from '../../interfaces'; import { convertStringsToBigInts } from '../../utils/helper'; +import useSnackbarStore from '../../store/useSnackbarStore'; interface IdentifierItemProps { identifier: { @@ -79,22 +84,24 @@ const IdentifierItem: React.FC = ({ )} {identifier.name} -
- {isRevealedPending ? ( - - ) : ( - <> - {isRevealed !== '*********' ? isRevealed : '*********'} - - {isRevealed !== '*********' ? ( - - ) : ( - - )} - - - )} -
+ {identifier.verified && ( +
+ {isRevealedPending ? ( + + ) : ( + <> + {isRevealed !== '*********' ? isRevealed : '*********'} + + {isRevealed !== '*********' ? ( + + ) : ( + + )} + + + )} +
+ )} } sx={{ ml: 2 }} @@ -103,7 +110,7 @@ const IdentifierItem: React.FC = ({ {identifier.verified ? ( + + ) : ( + <> + + Permissions + + + Note: The process of revoking or granting access to + applications may take some time. + + + + )} ); } diff --git a/src/services/api/index.ts b/src/services/api/index.ts index 8147533..2802abf 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -1,4 +1,6 @@ import axios from 'axios'; +import { useNavigate } from 'react-router-dom'; +import useSnackbarStore from '../../store/useSnackbarStore'; export const baseURL = import.meta.env.VITE_API_BASE_URL; @@ -35,5 +37,32 @@ apiInstance.interceptors.request.use( } ); +apiInstance.interceptors.response.use( + (response) => response, + (error) => { + const navigate = useNavigate(); + const { showSnackbar } = useSnackbarStore(); + console.log(error.response, 'test'); + + if (error.response?.status === 400) { + showSnackbar('Bad Request', { + severity: 'error', + }); + } + + if (error.response?.status === 401) { + // Show the snackbar message + showSnackbar('Session expired. Please log in again.', { + severity: 'warning', + }); + + // Redirect the user to the login page + navigate('/auth/login'); + } + + return Promise.reject(error); + } +); + export default apiInstance; export { apiInstance as api }; diff --git a/src/store/useSnackbarStore.ts b/src/store/useSnackbarStore.ts new file mode 100644 index 0000000..5fd469b --- /dev/null +++ b/src/store/useSnackbarStore.ts @@ -0,0 +1,38 @@ +import { SnackbarOrigin } from '@mui/material'; +import { create } from 'zustand'; + +interface SnackbarState { + message: string; + open: boolean; + options?: SnackbarOptions; + showSnackbar: (message: string, options?: SnackbarOptions) => void; + closeSnackbar: () => void; +} + +interface SnackbarOptions { + severity?: 'success' | 'error' | 'warning' | 'info'; + duration?: number; + position?: SnackbarOrigin; +} + +const useSnackbarStore = create((set) => ({ + message: '', + open: false, + options: { + severity: 'info', + duration: 3000, + position: { vertical: 'bottom', horizontal: 'right' }, + }, + showSnackbar: (message, options) => + set({ + message, + open: true, + options: { + ...options, + position: { vertical: 'bottom', horizontal: 'right' }, + }, + }), + closeSnackbar: () => set({ open: false }), +})); + +export default useSnackbarStore;