diff --git a/.env.example b/.env.example index 61d0107..fdc36c2 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ -VITE_APP_API_URL=backend_url \ No newline at end of file +VITE_APP_API_URL="" +VITE_PARENT_APP_ORIGIN="" +VITE_SUNBIRD_RC_URL_VCDATA="" \ No newline at end of file diff --git a/src/components/BottomNavigationBar.jsx b/src/components/BottomNavigationBar.jsx index 32b3da5..0e73e18 100644 --- a/src/components/BottomNavigationBar.jsx +++ b/src/components/BottomNavigationBar.jsx @@ -1,50 +1,81 @@ -import React from 'react'; -import { Box, Grid, IconButton, Typography } from '@mui/material'; -import HomeIcon from '@mui/icons-material/Home'; -import HistoryIcon from '@mui/icons-material/History'; -import GroupIcon from '../assets/action_key.svg'; -import { useNavigate } from 'react-router-dom'; +import { Box, Grid, IconButton, Typography } from "@mui/material"; +import HomeIcon from "@mui/icons-material/Home"; +import HistoryIcon from "@mui/icons-material/History"; +import GroupIcon from "../assets/action_key.svg"; +import { useNavigate } from "react-router-dom"; const BottomNavigationBar = () => { - const navigate= useNavigate(); + const navigate = useNavigate(); return ( - + {/* Home Icon */} - navigate('/home')} sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center'}}> - - + navigate("/home")} + sx={{ + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > + + Home - + {/* History Icon */} - + - + History - + {/* Group Icon */} - - Group + + Group {/* Group */} diff --git a/src/components/DocumentSelector.jsx b/src/components/DocumentSelector.jsx index 46079d6..fe01ea3 100644 --- a/src/components/DocumentSelector.jsx +++ b/src/components/DocumentSelector.jsx @@ -1,16 +1,27 @@ -import React, { useState, useEffect } from 'react'; -import Header from './Header'; -import BottomNavigationBar from './BottomNavigationBar'; -import { useNavigate } from 'react-router-dom'; -import { Box, Container, Typography, TextField, List, ListItem, ListItemText, Checkbox, Button, Paper } from '@mui/material'; -import { Search as SearchIcon } from '@mui/icons-material'; -import { styled } from '@mui/material/styles'; +import { useState, useEffect } from "react"; +import Header from "./Header"; +import BottomNavigationBar from "./BottomNavigationBar"; +import { useNavigate } from "react-router-dom"; +import { + Box, + Container, + Typography, + TextField, + List, + ListItem, + ListItemText, + Checkbox, + Button, + Paper, +} from "@mui/material"; +import { Search as SearchIcon } from "@mui/icons-material"; +import { styled } from "@mui/material/styles"; const StyledSearchBox = styled(Paper)(({ theme }) => ({ - padding: '2px 4px', - display: 'flex', - alignItems: 'center', - width: '100%', + padding: "2px 4px", + display: "flex", + alignItems: "center", + width: "100%", backgroundColor: "#E9E7EF", marginBottom: theme.spacing(2), borderRadius: 9, @@ -20,7 +31,7 @@ const DocumentSelector = () => { const navigate = useNavigate(); const [selectedDocs, setSelectedDocs] = useState([]); const [documents, setDocuments] = useState([]); - const [authToken, setAuthToken] = useState(localStorage.getItem('authToken')); + const [authToken, setAuthToken] = useState(localStorage.getItem("authToken")); const [isTokenReceived, setIsTokenReceived] = useState(false); const isEmbedded = window.self !== window.top; const handleToggle = (id) => { @@ -37,12 +48,14 @@ const DocumentSelector = () => { }; const handleImportClick = () => { - const selectedDocuments = documents.filter(doc => selectedDocs.includes(doc.doc_id)); + const selectedDocuments = documents.filter((doc) => + selectedDocs.includes(doc.doc_id) + ); const parentAppOrigin = import.meta.env.VITE_PARENT_APP_ORIGIN; - console.log('Selected Documents:', selectedDocuments); + console.log("Selected Documents:", selectedDocuments); window.parent.postMessage( - { type: 'selected-docs', data: selectedDocuments }, + { type: "selected-docs", data: selectedDocuments }, parentAppOrigin ); }; @@ -52,7 +65,9 @@ const DocumentSelector = () => { const apiUrl = `${import.meta.env.VITE_APP_API_URL}/user-docs/fetch`; if (!authToken) { - console.warn("Auth token not found, waiting for parent app to provide token..."); + console.warn( + "Auth token not found, waiting for parent app to provide token..." + ); return; } @@ -61,7 +76,7 @@ const DocumentSelector = () => { method: "GET", headers: { "Content-Type": "application/json", - "Authorization": `Bearer ${authToken}`, + Authorization: `Bearer ${authToken}`, }, }); @@ -86,19 +101,19 @@ const DocumentSelector = () => { const messageListener = (event) => { const data = event.data; - if (data.type === 'JWT_TOKEN' && data.payload) { + if (data.type === "JWT_TOKEN" && data.payload) { const jwtToken = data.payload; console.log("Received JWT Token from parent:", jwtToken); - localStorage.setItem('authToken', jwtToken); + localStorage.setItem("authToken", jwtToken); setAuthToken(jwtToken); setIsTokenReceived(true); } }; - window.addEventListener('message', messageListener); + window.addEventListener("message", messageListener); return () => { - window.removeEventListener('message', messageListener); + window.removeEventListener("message", messageListener); }; }, [isEmbedded]); @@ -106,21 +121,20 @@ const DocumentSelector = () => { if (isEmbedded) return; if (!authToken && !isTokenReceived) { localStorage.setItem("login-redirect", window.location.pathname); - navigate('/login'); + navigate("/login"); } }, [authToken, isTokenReceived, isEmbedded, navigate]); return (
- + {/* Search Box */} { sx={{ ml: 1 }} /> - - + Please choose your required document. {/* Document List */} + {documents.map((doc) => ( @@ -168,7 +186,6 @@ const DocumentSelector = () => { ))} - {/* Import Button */} - + ); }; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 50ad368..d8581bf 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,19 +1,20 @@ -import React, { useState } from "react"; -import { useLocation } from "react-router-dom"; +import { useState } from "react"; import { AppBar, Toolbar, Typography, FormControl, Select, - MenuItem, + MenuItem, } from "@mui/material"; import { languages } from "../config"; import SlideMenu from "./SlideMenu"; const Header = () => { // Define the state to store selected language const [language, setLanguage] = useState("EN"); - const shouldShowSlideMenu = !['/signup', '/login'].includes(location.pathname); + const shouldShowSlideMenu = !["/signup", "/login"].includes( + location.pathname + ); const handleLanguageChange = (event) => { setLanguage(event.target.value); @@ -21,10 +22,10 @@ const Header = () => { return ( {/* Menu */} @@ -47,7 +48,7 @@ const Header = () => { { return ( - + <>
- + - + - + ); }; diff --git a/src/components/MainContent.jsx b/src/components/MainContent.jsx index d02558f..dd40217 100644 --- a/src/components/MainContent.jsx +++ b/src/components/MainContent.jsx @@ -1,66 +1,121 @@ -import React,{useState,useEffect} from "react"; +import { useState, useEffect } from "react"; import { List, ListItem, - ListItemIcon, - ListItemText, Box, Paper, FormHelperText, Container, - Typography + Typography, + Tooltip, + IconButton, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Alert, } from "@mui/material"; -import NoDocuments from '../assets/NoDocuments.png'; +import NoDocuments from "../assets/NoDocuments.png"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import DeleteIcon from "@mui/icons-material/Delete"; +import VisibilityIcon from "@mui/icons-material/Visibility"; + +import FloatingActionButton from "./FloatingActionButton"; +import axios from "axios"; const MainContent = () => { // Document list data const [documents, setDocuments] = useState([]); - const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - const fetchDocuments = async () => { - // Retrieve the auth token from localStorage - const authToken = localStorage.getItem("authToken"); - - if (!authToken) { - setError("No authorization token found."); - setLoading(false); - return; - } + const [success, setSuccess] = useState(null); + const [open, setOpen] = useState(false); + const [documentName, setDocumentName] = useState(); + const [openPreview, setOpenPreview] = useState(false); + const fetchDocuments = async () => { + // Retrieve the auth token from localStorage + const authToken = localStorage.getItem("authToken"); - const apiUrl = `${import.meta.env.VITE_APP_API_URL}/user-docs/fetch`; - - try { - const response = await fetch(apiUrl, { - method: "GET", - headers: { - "Authorization": `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch documents"); - } - - const data = await response.json(); - setDocuments(data); - setLoading(false); - } catch (err) { - setError(err.message); - setLoading(false); + if (!authToken) { + setError("No authorization token found."); + return; + } + + const apiUrl = `${import.meta.env.VITE_APP_API_URL}/user-docs/fetch`; + + try { + const response = await fetch(apiUrl, { + method: "GET", + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch documents"); } - }; + const data = await response.json(); + + setDocuments(data); + } catch (err) { + setError(err.message); + setTimeout(() => { + setError(null); + }, 5000); + } + }; + + useEffect(() => { fetchDocuments(); - }, []); + }, []); + const handleClose = () => { + setOpen(false); + }; + const handleOpen = (doc) => { + setDocumentName(doc); + setOpen(true); + }; + const handlePreviewClick = (doc) => { + setOpenPreview(JSON.parse(doc?.doc_data)); + }; + const handleDelete = async () => { + const authToken = localStorage.getItem("authToken"); + console.log(" document?.doc_id", documentName?.doc_id); + + try { + const url = `${import.meta.env.VITE_APP_API_URL}/user-docs/delete/${ + documentName?.doc_id + }`; + const headers = { + Authorization: `Bearer ${authToken}`, + }; + + const response = await axios.delete(url, { headers }); + handleClose(); + setSuccess(true); + setTimeout(() => { + setSuccess(null); + }, 5000); + fetchDocuments(); - if (documents.length>0) { + return response.data; + } catch (error) { + handleClose(); + setError(error.errorMessage); + setTimeout(() => { + setError(null); + }, 5000); + } + }; + if (documents.length > 0) { return ( - - + + {documents.map((doc, index) => { // Parse the doc_data string to get the id @@ -71,28 +126,69 @@ const MainContent = () => { disablePadding sx={{ py: 1.5, - borderBottom: index !== documents.length - 1 ? "1px solid" : "none", - borderColor: "divider", + borderBottom: + index !== documents.length - 1 ? "1px solid" : "none", + borderColor: "#DDDDDD", display: "flex", alignItems: "flex-start", + width: "100%", }} > - + - - - + + + {/* Document Text */} + - + }} + > + {doc.doc_name} + + + {/* Tooltip with Delete Icon */} + + + handlePreviewClick(doc)} + > + + + + + handleOpen(doc)} + > + + + + + + ID: {doc.doc_id} @@ -101,6 +197,63 @@ const MainContent = () => { })} + + + + Confirm Delete + + + Are you sure you want to delete the document{" "} + {documentName?.doc_name}? This action cannot be + undone. + + + + + + + + {/* Preview Dialog */} + setOpenPreview(false)} + maxWidth="md" + fullWidth + > + JSON Preview + +
+              {openPreview
+                ? JSON.stringify(openPreview, null, 2)
+                : "No content to display."}{" "}
+            
+
+ + + +
+ {error && ( + This is an error Alert.{error} + )} + {success && ( + Document deleted successfully + )}
); } @@ -111,10 +264,10 @@ const MainContent = () => { sx={{ pt: 8, pb: 10, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - textAlign: 'center', + display: "flex", + flexDirection: "column", + alignItems: "center", + textAlign: "center", flexGrow: 1, }} > @@ -123,10 +276,18 @@ const MainContent = () => { alt="No Documents" style={{ width: 260, height: 210 }} /> - + Bring Your Digital Identity - + Tap on the "+" icon below to add your documents to this wallet