+
diff --git a/src/components/templates/TemplateTable.tsx b/src/components/templates/TemplateTable.tsx
index 38bc1ea..298aebc 100644
--- a/src/components/templates/TemplateTable.tsx
+++ b/src/components/templates/TemplateTable.tsx
@@ -1,285 +1,289 @@
-import { useEffect, useState } from 'react'
-import { useSelector } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
-import logoRecord from '../../assets/record.svg';
-import LoaderUi from '../../helper/LoaderUi';
-import api from '../../config/api';
-import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
-import { TemplateInterface } from '../../utlity/types';
-import TemplateDeleteWarnModal from './TemplateDeleteWarnModal';
-import Loader from '../dashboard/Loader';
+import React, { useEffect, useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import { PiDotsThreeOutlineVerticalFill } from "react-icons/pi";
+import logoRecord from "../../assets/record.svg";
+import Loader from "../dashboard/Loader";
+import api from "../../config/api";
+import TemplateDeleteWarnModal from "./TemplateDeleteWarnModal";
+
+interface Template {
+ id: number;
+ name: string | null;
+ description: string | null;
+ createdAt: string;
+ lastModifiedAt: string;
+ createdBy?: { name: string };
+ lastModifiedBy?: { name: string };
+ expireAfter?: number;
+}
+
+const formatDate = (isoString: string) => {
+ const date = new Date(isoString);
+ return isNaN(date.getTime()) ? "-" : date.toLocaleString();
+};
const TemplateTable = () => {
- const [loading, setLoading] = useState(false)
- const [templates, setTemplates] = useState
([])
- const accountDetails = useSelector((state: any) => state.user.account);
- const accountId = accountDetails.id || "";
- const [showMenu, setShowMenu] = useState(null);
- const [showTemplateDeleteWarnModal, setShowTemplateDeleteWarnModal] = useState<{ show: boolean, templateId: string }>({ show: false, templateId: "" });
const [activeAccountId, setActiveAccountId] = useState(
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
);
const navigate = useNavigate();
+ const location = useLocation();
- const getAllTemplates = async () => {
- setLoading(true)
+ const [templates, setTemplates] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ // Pagination
+ const [page, setPage] = useState(0); // 0-based
+ const [size, setSize] = useState(10);
+ const [totalPages, setTotalPages] = useState(0);
+
+ // Menu state
+ const [showMenu, setShowMenu] = useState<{ open: boolean; templateId: number | null }>({
+ open: false,
+ templateId: null,
+ });
+
+ // Delete modal
+ const [showTemplateDeleteWarnModal, setShowTemplateDeleteWarnModal] = useState<{
+ show: boolean;
+ templateId: string;
+ }>({ show: false, templateId: "" });
+
+ const fetchTemplates = async () => {
+ if (!activeAccountId) return;
+ setLoading(true);
try {
- let res = await api.get(`/api/v1/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc`)
- let templateData = res?.data
- setTemplates(templateData)
+ const res = await api.get(
+ `/api/v1/accounts/${activeAccountId}/templates?sortBy=lastModifiedAt desc&page=${page}&size=${size}`
+ );
+
+ const { content = [], page: pageData = {} } = res.data;
+ setTemplates(content);
+ setTotalPages(pageData.totalPages || 0);
} catch (error) {
- console.log(error)
+ console.error("Error fetching templates:", error);
} finally {
- setLoading(false)
+ setLoading(false);
}
- }
-
- useEffect(() => {
- if (activeAccountId) {
- getAllTemplates()
- }
- }, [activeAccountId])
-
-
- // Listen for sessionStorage changes
- useEffect(() => {
- const handleStorageChange = (e: StorageEvent) => {
- if (e.key === 'activeAccountId') {
- const newActiveAccountId = e.newValue ? JSON.parse(e.newValue) : null;
- setActiveAccountId(newActiveAccountId);
- }
- };
-
- // Listen for storage events (works for changes from other tabs)
- window.addEventListener('storage', handleStorageChange);
-
- // Custom event listener for same-tab changes
- const handleCustomStorageChange = (e: CustomEvent) => {
- if (e.detail.key === 'activeAccountId') {
- setActiveAccountId(e.detail.newValue);
- }
- };
-
- window.addEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
-
- return () => {
- window.removeEventListener('storage', handleStorageChange);
- window.removeEventListener('sessionStorageChange', handleCustomStorageChange as EventListener);
- };
- }, []);
-
-
- const formatDate = (timestamp: number) => {
- return new Date(timestamp * 1000).toLocaleString();
};
- const handleUseClick = async (item: any) => {
+ useEffect(() => {
+ fetchTemplates();
+ }, [activeAccountId, page, size]);
+
+ const handleUseClick = async (item: Template) => {
try {
const payload = {
- name: item?.name ?? "",
- subject: item.subject,
- message: item.message,
- description: item.description,
+ name: item.name ?? "",
+ subject: item.description,
+ message: "",
expireAfter: item.expireAfter,
- enableIdStamping: item.enableIdStamping,
- allowViewAudit: item.allowViewAudit,
- allowReassign: item.allowReassign,
- enableSignatureIdStamping: item.enableSignatureIdStamping,
templateId: item.id,
};
- let res = await api.post(`/api/v1/accounts/${activeAccountId}/transactions`, payload)
-
- // /process-step-one/:accountId/:transactionId
+ const res = await api.post(`/api/v1/accounts/${activeAccountId}/transactions`, payload);
navigate(`/process-step-one/${activeAccountId}/${res?.data?.id}?useTemplate=true`);
} catch (error) {
- console.log(error)
+ console.error(error);
}
- }
+ };
const handleEditClick = (templateId: string) => {
- setShowMenu(null);
- navigate(`/process-step-one/${accountId}/template/${templateId}`);
- }
+ setShowMenu({ open: false, templateId: null });
+ navigate(`/process-step-one/${activeAccountId}/template/${templateId}`);
+ };
+
const handleDeleteClick = async (templateId: string) => {
- setShowMenu(null);
+ setShowMenu({ open: false, templateId: null });
try {
- await api.delete(`api/accounts/${activeAccountId}/templates/${templateId}`)
- setTemplates((prev) => {
- return prev.filter((temp) => temp.id !== Number(templateId));
- });
- setShowTemplateDeleteWarnModal({ show: false, templateId: "" })
- } catch (e) { }
- }
-
- function formatTimestamp(isoString, options = { timeZone: 'local' }) {
- // Parse the ISO string to a Date object
- const date = new Date(isoString);
-
- if (isNaN(date)) {
- return 'Invalid date';
+ await api.delete(`/api/v1/accounts/${activeAccountId}/templates/${templateId}`);
+ setTemplates((prev) => prev.filter((temp) => temp.id !== Number(templateId)));
+ setShowTemplateDeleteWarnModal({ show: false, templateId: "" });
+ } catch (e) {
+ console.error(e);
}
+ };
- // Return formatted date string
- if (options.timeZone === 'UTC') {
- return date.toUTCString(); // e.g. "Mon, 04 Aug 2025 13:46:46 GMT"
- } else {
- return date.toLocaleString(); // e.g. "8/4/2025, 9:46:46 AM" (based on local time zone)
- }
- }
return (
- <>
-
- {/* Header */}
- {location.pathname !== '/templates' &&
-
-
Recent Template
- View all
- }
+
+ {/* Header */}
+ {location.pathname !== "/templates" && (
+
+
Recent Templates
+ navigate("/templates")}
+ className="text-[var(--primary)] text-sm hover:underline cursor-pointer font-medium"
+ >
+ View all
+
+
+ )}
- {/* Scrollable Table Container */}
-
-
- {/* Sticky Header */}
-
-
- | Name |
- {location.pathname === '/templates' && (
- <>
- Created |
- Last Updated |
- >
- )}
- {/* Use | */}
- Action |
+ {/* Table */}
+
+
+
+
+ | Template Name |
+ Created By |
+ Created |
+ Last Updated |
+ Actions |
+
+
+
+ {loading ? (
+
+
+ |
+
+ |
-
- {loading ? (
-
-
- |
-
+ |
+ ) : templates.length > 0 ? (
+
+ {templates.map((template, idx) => (
+
+ |
+ {template.name || "Untitled"}
|
-
-
- ) : templates.length > 0 ? (
- <>
- {/* Scrollable Body */}
-
- {templates.map((item, index) => (
-
- |
-
- {item.name || 'Untitled'}
-
- |
+
+ {template.createdBy?.name || "-"}
+ |
+ {formatDate(template.createdAt)} |
+
+ {formatDate(template.lastModifiedAt)}
+ |
+
+
+
- {location.pathname === '/templates' && (
- <>
-
-
- {formatTimestamp(item.createdAt)}
-
- |
-
-
- {formatTimestamp(item.lastModifiedAt)}
-
- |
- >
- )}
-
- {/*
-
- | */}
-
-
- {
- if (!e.currentTarget.contains(e.relatedTarget)) {
- setShowMenu(null);
- }
- }}
- >
-
+ {showMenu.open && showMenu.templateId === template.id && (
+
+
+
-
- {showMenu === index && (
-
-
-
-
-
- )}
-
- |
- |
- ))}
-
- >
- ) : (
-
-
-
-
- 
+ )}
|
-
- )}
-
-
-
- {
- showTemplateDeleteWarnModal.show && handleDeleteClick(showTemplateDeleteWarnModal.templateId)}
- />
- }
+ ))}
+
+ ) : (
+
+
+
+
+ 
+ No templates found
+
+ |
+
+
+ )}
+
- >
- )
-}
-export default TemplateTable
\ No newline at end of file
+ {/* Pagination */}
+ {templates.length > 0 && (
+
+
+ Page {page + 1} of {totalPages}
+
+
+
+
+
+
+
+
+ )}
+
+ {showTemplateDeleteWarnModal.show && (
+
handleDeleteClick(showTemplateDeleteWarnModal.templateId)}
+ />
+ )}
+
+ );
+};
+
+export default TemplateTable;
diff --git a/src/components/transactions/TransactionView.tsx b/src/components/transactions/TransactionView.tsx
index 406c650..3391886 100644
--- a/src/components/transactions/TransactionView.tsx
+++ b/src/components/transactions/TransactionView.tsx
@@ -114,7 +114,7 @@ const TransactionView = () => {
const handleNavigateToSign = (id: number) => {
- signSessionRedirect(accountId, transactionId, id+"" as string)
+ signSessionRedirect(accountId, transactionId, id + "" as string)
}
const getStatusBadge = (status: string) => {
@@ -202,18 +202,21 @@ const TransactionView = () => {
Host: {recipient.hostEmail}
)}
+ {"message" in recipient &&
+ {recipient.message}
+
}
{/* Right section: status and actions */}
-
+ {recipient.type.toLowerCase() !== "cc" &&
- {transactionDetails.transaction.status === "SENT" && recipient.email === user.email && recipient.status !== "COMPLETED" && (
+ {transactionDetails.transaction.status === "SENT" && recipient.email === user.email && recipient.status === "PENDING" && (
+
}
@@ -274,12 +277,12 @@ const TransactionView = () => {
{/* 1 of {docsD?.pages?.length} */}
-
- downloadDocumentUtils(accountId, transactionId, docsD.id)}
size={18} />
-
+
diff --git a/src/config/api.tsx b/src/config/api.tsx
index fc46c37..7532c7a 100644
--- a/src/config/api.tsx
+++ b/src/config/api.tsx
@@ -1,10 +1,9 @@
// src/api.js
import axios from "axios";
-import { redirect } from "react-router-dom"; // if using react-router
-// or use your preferred navigation method
const api = axios.create({
baseURL: import.meta.env.VITE_BASE_URI,
+ maxRedirects: 5,
withCredentials: true,
headers: {
"Content-Type": "application/json",
diff --git a/src/config/axiosInstance.tsx b/src/config/axiosInstance.tsx
index 74de498..fa32d9c 100644
--- a/src/config/axiosInstance.tsx
+++ b/src/config/axiosInstance.tsx
@@ -1,31 +1,46 @@
-import axios from "axios";
+// api/axios.ts
+import axios, { AxiosInstance } from "axios";
-export const createAxiosInstance = () => {
+const defaultHeaders = {
+ "Content-Type": "application/json",
+ "ngrok-skip-browser-warning": "69420",
+};
- const persistedRoot = localStorage.getItem('persist:root');
- const parsedRoot = JSON.parse(persistedRoot);
+const axiosInstance: AxiosInstance = axios.create({
+ baseURL: import.meta.env.VITE_BASE_URI,
+ withCredentials: true,
+ headers: defaultHeaders,
+ validateStatus: (status) => status >= 200 && status < 400,
+});
- if(!persistedRoot || !("user" in parsedRoot)) {
- return axios.create({
- baseURL: import.meta.env.VITE_BASE_URI,
- withCredentials: true,
- headers: {
- "Content-Type": "application/json",
- "ngrok-skip-browser-warning": "69420",
- },
- });
+
+axiosInstance.interceptors.response.use(
+ (response) => {
+ // ✅ Handle backend "soft redirects"
+ if (response?.data?.redirect) {
+ const redirectUrl = response.data.redirect;
+
+ // Use location.replace to avoid back button returning to invalid page
+ window.location.replace(redirectUrl);
+ // OR window.location.href = redirectUrl; if you want history entry
+
+ // Optionally return a rejected promise to stop normal handling
+ return Promise.reject({ redirected: true });
}
- const user = JSON.parse(parsedRoot.user);
- const accessToken = user.accessToken;
+ // Otherwise just continue as normal
+ return response;
+ },
+ (error) => {
+ // Optionally handle backend 302 or 3xx here (if returned as error)
+ if (error.response?.status === 302 && error.response?.headers?.location) {
+ window.location.replace(error.response.headers.location);
+ return Promise.reject({ redirected: true });
+ }
- return axios.create({
- baseURL: import.meta.env.VITE_BASE_URI,
- withCredentials: true,
- headers: {
- "ngrok-skip-browser-warning": "69420",
- authorization: accessToken,
+ // Pass through all other errors
+ return Promise.reject(error);
+ }
+);
- },
- });
-};
+export default axiosInstance;
diff --git a/src/store/slices/selectedDocsSlice.ts b/src/store/slices/selectedDocsSlice.ts
index 73b2e9e..1075d1d 100644
--- a/src/store/slices/selectedDocsSlice.ts
+++ b/src/store/slices/selectedDocsSlice.ts
@@ -1,8 +1,4 @@
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
-import axios from "axios";
-import { createAxiosInstance } from "../../config/axiosInstance";
-
-const axiosInstance = createAxiosInstance();
export type SignType = "self-sign" | "multiple-sign";