selenium being fixed
This commit is contained in:
@@ -4,10 +4,7 @@ import { storage } from "../storage";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
import { ClaimUncheckedCreateInputObjectSchema } from "@repo/db/usedSchemas";
|
||||||
import multer from "multer";
|
import multer from "multer";
|
||||||
import {
|
import { forwardToSeleniumAgent } from "../services/seleniumClient";
|
||||||
forwardToSeleniumAgent,
|
|
||||||
forwardToSeleniumAgent2,
|
|
||||||
} from "../services/seleniumClient";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -110,9 +107,11 @@ router.post(
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
res.json({ success: true, data: result });
|
res.json({ success: true, data: result });
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
res.status(500).json({ error: "Failed to forward to selenium agent" });
|
return res.status(500).json({
|
||||||
|
error: err.message || "Failed to forward to selenium agent",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -120,30 +119,34 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
"/selenium/fetchpdf",
|
"/selenium/fetchpdf",
|
||||||
async (req: Request, res: Response): Promise<any> => {
|
async (req: Request, res: Response): Promise<any> => {
|
||||||
if (!req.user || !req.user.id) {
|
function sendError(res: Response, message: string, status = 400) {
|
||||||
return res.status(401).json({ error: "Unauthorized: user info missing" });
|
console.error("Error:", message);
|
||||||
|
return res.status(status).json({ error: message });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { patientId, claimId } = req.body;
|
if (!req.user || !req.user.id) {
|
||||||
|
return sendError(res, "Unauthorized: user info missing", 401);
|
||||||
if (!patientId || !claimId) {
|
|
||||||
return res.status(400).json({ error: "Missing patientId or claimId" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { patientId, claimId, pdf_url } = req.body;
|
||||||
|
|
||||||
|
if (!pdf_url) {
|
||||||
|
return sendError(res, "Missing pdf_url");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!patientId) {
|
||||||
|
return sendError(res, "Missing Patient Id");
|
||||||
|
}
|
||||||
|
if (!claimId) {
|
||||||
|
return sendError(res, "Missing Claim Id");
|
||||||
|
}
|
||||||
|
|
||||||
const parsedPatientId = parseInt(patientId);
|
const parsedPatientId = parseInt(patientId);
|
||||||
const parsedClaimId = parseInt(claimId);
|
const parsedClaimId = parseInt(claimId);
|
||||||
|
|
||||||
const result = await forwardToSeleniumAgent2();
|
const filename = path.basename(new URL(pdf_url).pathname);
|
||||||
|
const pdfResponse = await axios.get(pdf_url, {
|
||||||
if (result.status !== "success") {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ error: result.message || "Failed to fetch PDF" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const pdfUrl = result.pdf_url;
|
|
||||||
const filename = path.basename(new URL(pdfUrl).pathname);
|
|
||||||
const pdfResponse = await axios.get(pdfUrl, {
|
|
||||||
responseType: "arraybuffer",
|
responseType: "arraybuffer",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,14 +169,12 @@ router.post(
|
|||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
pdfPath: `/temp/${filename}`,
|
pdfPath: `/temp/${filename}`,
|
||||||
pdfUrl,
|
pdf_url,
|
||||||
fileName: filename,
|
fileName: filename,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error in /selenium/fetchpdf:", err);
|
console.error("Error in /selenium/fetchpdf:", err);
|
||||||
return res
|
return sendError(res, "Failed to Fetch and Download the pdf", 500);
|
||||||
.status(500)
|
|
||||||
.json({ error: "Failed to forward to selenium agent 2" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -36,14 +36,17 @@ export async function forwardToSeleniumAgent(
|
|||||||
images,
|
images,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await axios.post(
|
const result = await axios.post(
|
||||||
"http://localhost:5002/start-workflow",
|
"http://localhost:5002/start-workflow",
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
return response.data;
|
if (result.data.status === "error") {
|
||||||
}
|
const errorMsg =
|
||||||
|
typeof result.data.message === "string"
|
||||||
|
? result.data.message
|
||||||
|
: result.data.message?.msg || "Selenium agent error";
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
export async function forwardToSeleniumAgent2(): Promise<any> {
|
return result.data;
|
||||||
const response = await axios.post("http://localhost:5002/fetch-pdf");
|
}
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Switch, Route } from "wouter";
|
import { Switch, Route } from "wouter";
|
||||||
import React, { Suspense, lazy } from "react";
|
import React, { Suspense, lazy } from "react";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { store } from "./redux/store";
|
||||||
import { queryClient } from "./lib/queryClient";
|
import { queryClient } from "./lib/queryClient";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { Toaster } from "./components/ui/toaster";
|
import { Toaster } from "./components/ui/toaster";
|
||||||
import { TooltipProvider } from "./components/ui/tooltip";
|
import { TooltipProvider } from "./components/ui/tooltip";
|
||||||
import { ProtectedRoute } from "./lib/protected-route";
|
import { ProtectedRoute } from "./lib/protected-route";
|
||||||
import { AuthProvider } from "./hooks/use-auth";
|
import { AuthProvider } from "./hooks/use-auth";
|
||||||
import Dashboard from "./pages/dashboard";
|
import Dashboard from "./pages/dashboard";
|
||||||
import LoadingScreen from "./components/ui/LoadingScreen";
|
import LoadingScreen from "./components/ui/LoadingScreen";
|
||||||
|
|
||||||
const AuthPage = lazy(() => import("./pages/auth-page"));
|
const AuthPage = lazy(() => import("./pages/auth-page"));
|
||||||
@@ -14,7 +16,9 @@ const AppointmentsPage = lazy(() => import("./pages/appointments-page"));
|
|||||||
const PatientsPage = lazy(() => import("./pages/patients-page"));
|
const PatientsPage = lazy(() => import("./pages/patients-page"));
|
||||||
const SettingsPage = lazy(() => import("./pages/settings-page"));
|
const SettingsPage = lazy(() => import("./pages/settings-page"));
|
||||||
const ClaimsPage = lazy(() => import("./pages/claims-page"));
|
const ClaimsPage = lazy(() => import("./pages/claims-page"));
|
||||||
const PreAuthorizationsPage = lazy(() => import("./pages/preauthorizations-page"));
|
const PreAuthorizationsPage = lazy(
|
||||||
|
() => import("./pages/preauthorizations-page")
|
||||||
|
);
|
||||||
const PaymentsPage = lazy(() => import("./pages/payments-page"));
|
const PaymentsPage = lazy(() => import("./pages/payments-page"));
|
||||||
const DocumentPage = lazy(() => import("./pages/documents-page"));
|
const DocumentPage = lazy(() => import("./pages/documents-page"));
|
||||||
const NotFound = lazy(() => import("./pages/not-found"));
|
const NotFound = lazy(() => import("./pages/not-found"));
|
||||||
@@ -23,32 +27,39 @@ function Router() {
|
|||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<ProtectedRoute path="/" component={() => <Dashboard />} />
|
<ProtectedRoute path="/" component={() => <Dashboard />} />
|
||||||
<ProtectedRoute path="/appointments" component={() => <AppointmentsPage />} />
|
<ProtectedRoute
|
||||||
|
path="/appointments"
|
||||||
|
component={() => <AppointmentsPage />}
|
||||||
|
/>
|
||||||
<ProtectedRoute path="/patients" component={() => <PatientsPage />} />
|
<ProtectedRoute path="/patients" component={() => <PatientsPage />} />
|
||||||
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
|
<ProtectedRoute path="/settings" component={() => <SettingsPage />} />
|
||||||
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
|
<ProtectedRoute path="/claims" component={() => <ClaimsPage />} />
|
||||||
<ProtectedRoute path="/preauthorizations" component={() => <PreAuthorizationsPage />} />
|
<ProtectedRoute
|
||||||
|
path="/preauthorizations"
|
||||||
|
component={() => <PreAuthorizationsPage />}
|
||||||
|
/>
|
||||||
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
|
<ProtectedRoute path="/payments" component={() => <PaymentsPage />} />
|
||||||
<ProtectedRoute path="/documents" component={() => <DocumentPage/>}/>
|
<ProtectedRoute path="/documents" component={() => <DocumentPage />} />
|
||||||
<Route path="/auth" component={() => <AuthPage />} />
|
<Route path="/auth" component={() => <AuthPage />} />
|
||||||
<Route component={() => <NotFound />} />
|
<Route component={() => <NotFound />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<Provider store={store}>
|
||||||
<AuthProvider>
|
<QueryClientProvider client={queryClient}>
|
||||||
<TooltipProvider>
|
<AuthProvider>
|
||||||
<Toaster />
|
<TooltipProvider>
|
||||||
<Suspense fallback={<LoadingScreen />}>
|
<Toaster />
|
||||||
<Router />
|
<Suspense fallback={<LoadingScreen />}>
|
||||||
</Suspense>
|
<Router />
|
||||||
</TooltipProvider>
|
</Suspense>
|
||||||
</AuthProvider>
|
</TooltipProvider>
|
||||||
</QueryClientProvider>
|
</AuthProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
50
apps/Frontend/src/components/claims/selenium-task-banner.tsx
Normal file
50
apps/Frontend/src/components/claims/selenium-task-banner.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { RootState } from "@/redux/store";
|
||||||
|
import { clearTaskStatus } from "@/redux/slices/seleniumTaskSlice";
|
||||||
|
import { Loader2, CheckCircle, XCircle } from "lucide-react";
|
||||||
|
|
||||||
|
export const SeleniumTaskBanner = () => {
|
||||||
|
const { status, message, show } = useSelector(
|
||||||
|
(state: RootState) => state.seleniumTask
|
||||||
|
);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
if (!show) return null;
|
||||||
|
|
||||||
|
const getIcon = () => {
|
||||||
|
switch (status) {
|
||||||
|
case "pending":
|
||||||
|
return <Loader2 className="w-5 h-5 animate-spin text-blue-500" />;
|
||||||
|
case "success":
|
||||||
|
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
||||||
|
case "error":
|
||||||
|
return <XCircle className="w-5 h-5 text-red-500" />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white border border-gray-200 shadow-md rounded-lg p-3 mb-4 flex items-start justify-between">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
{getIcon()}
|
||||||
|
<div>
|
||||||
|
<div className="font-medium text-gray-800">
|
||||||
|
{status === "pending"
|
||||||
|
? "Selenium Task In Progress"
|
||||||
|
: status === "success"
|
||||||
|
? "Selenium Task Completed"
|
||||||
|
: "Selenium Task Error"}
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 text-sm">{message}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => dispatch(clearTaskStatus())}
|
||||||
|
className="text-sm text-gray-500 hover:text-gray-800"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,6 +18,12 @@ import { apiRequest, queryClient } from "@/lib/queryClient";
|
|||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import RecentClaims from "@/components/claims/recent-claims";
|
import RecentClaims from "@/components/claims/recent-claims";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import {
|
||||||
|
setTaskStatus,
|
||||||
|
clearTaskStatus,
|
||||||
|
} from "@/redux/slices/seleniumTaskSlice";
|
||||||
|
import { SeleniumTaskBanner } from "@/components/claims/selenium-task-banner";
|
||||||
|
|
||||||
//creating types out of schema auto generated.
|
//creating types out of schema auto generated.
|
||||||
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
type Appointment = z.infer<typeof AppointmentUncheckedCreateInputObjectSchema>;
|
||||||
@@ -71,8 +77,8 @@ type UpdatePatient = z.infer<typeof updatePatientSchema>;
|
|||||||
export default function ClaimsPage() {
|
export default function ClaimsPage() {
|
||||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||||
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
|
const [isClaimFormOpen, setIsClaimFormOpen] = useState(false);
|
||||||
const [isMhPopupOpen, setIsMhPopupOpen] = useState(false);
|
|
||||||
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
const [selectedPatient, setSelectedPatient] = useState<number | null>(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const [claimFormData, setClaimFormData] = useState<any>({
|
const [claimFormData, setClaimFormData] = useState<any>({
|
||||||
@@ -215,38 +221,6 @@ export default function ClaimsPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// selenium pdf download handler
|
|
||||||
const handleSeleniumPopup = async (actionType: string) => {
|
|
||||||
try {
|
|
||||||
if (!claimRes?.id) {
|
|
||||||
throw new Error("Missing claimId");
|
|
||||||
}
|
|
||||||
if (!selectedPatient) {
|
|
||||||
throw new Error("Missing patientId");
|
|
||||||
}
|
|
||||||
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
|
|
||||||
action: actionType,
|
|
||||||
patientId: selectedPatient,
|
|
||||||
claimId: claimRes.id,
|
|
||||||
});
|
|
||||||
const result = await res.json();
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Success",
|
|
||||||
description: "MH action completed.",
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsMhPopupOpen(false);
|
|
||||||
setSelectedPatient(null);
|
|
||||||
} catch (error: any) {
|
|
||||||
toast({
|
|
||||||
title: "Error",
|
|
||||||
description: error.message,
|
|
||||||
variant: "destructive",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Converts local date to exact UTC date with no offset issues
|
// Converts local date to exact UTC date with no offset issues
|
||||||
function parseLocalDate(dateInput: Date | string): Date {
|
function parseLocalDate(dateInput: Date | string): Date {
|
||||||
if (dateInput instanceof Date) return dateInput;
|
if (dateInput instanceof Date) return dateInput;
|
||||||
@@ -332,6 +306,7 @@ export default function ClaimsPage() {
|
|||||||
return res.json();
|
return res.json();
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
console.log(data)
|
||||||
setClaimRes(data);
|
setClaimRes(data);
|
||||||
toast({
|
toast({
|
||||||
title: "Claim submitted successfully",
|
title: "Claim submitted successfully",
|
||||||
@@ -519,7 +494,6 @@ export default function ClaimsPage() {
|
|||||||
const handleSelenium = async (data: any) => {
|
const handleSelenium = async (data: any) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("data", JSON.stringify(data));
|
formData.append("data", JSON.stringify(data));
|
||||||
|
|
||||||
const uploadedFiles: File[] = data.uploadedFiles ?? [];
|
const uploadedFiles: File[] = data.uploadedFiles ?? [];
|
||||||
|
|
||||||
uploadedFiles.forEach((file: File) => {
|
uploadedFiles.forEach((file: File) => {
|
||||||
@@ -531,22 +505,43 @@ export default function ClaimsPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "pending",
|
||||||
|
message: "Submitting claim to Selenium...",
|
||||||
|
})
|
||||||
|
);
|
||||||
const response = await apiRequest(
|
const response = await apiRequest(
|
||||||
"POST",
|
"POST",
|
||||||
"/api/claims/selenium",
|
"/api/claims/selenium",
|
||||||
formData
|
formData
|
||||||
);
|
);
|
||||||
const result = await response.json();
|
const result1 = await response.json();
|
||||||
|
if (result1.error) throw new Error(result1.error);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "pending",
|
||||||
|
message: "Submitted to Selenium. Awaiting PDF...",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Selenium service notified",
|
title: "Selenium service notified",
|
||||||
description:
|
description:
|
||||||
"Your claim data was successfully sent to Selenium, Wait for its response.",
|
"Your claim data was successfully sent to Selenium, Waitinig for its response.",
|
||||||
variant: "default",
|
variant: "default",
|
||||||
});
|
});
|
||||||
setIsMhPopupOpen(true);
|
|
||||||
return result;
|
// const result2 = await handleSeleniumPdfDownload(result1);
|
||||||
|
// return result2;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "error",
|
||||||
|
message: error.message || "Selenium submission failed",
|
||||||
|
})
|
||||||
|
);
|
||||||
toast({
|
toast({
|
||||||
title: "Selenium service error",
|
title: "Selenium service error",
|
||||||
description: error.message || "An error occurred.",
|
description: error.message || "An error occurred.",
|
||||||
@@ -555,6 +550,61 @@ export default function ClaimsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// selenium pdf download handler
|
||||||
|
const handleSeleniumPdfDownload = async (data: any) => {
|
||||||
|
try {
|
||||||
|
if (!claimRes?.id) {
|
||||||
|
throw new Error("Missing claimId2s");
|
||||||
|
}
|
||||||
|
if (!selectedPatient) {
|
||||||
|
throw new Error("Missing patientId");
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "pending",
|
||||||
|
message: "Downloading PDF from Selenium...",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const res = await apiRequest("POST", "/api/claims/selenium/fetchpdf", {
|
||||||
|
patientId: selectedPatient,
|
||||||
|
claimId: claimRes.id,
|
||||||
|
pdf_url: data["pdf_url"],
|
||||||
|
});
|
||||||
|
const result = await res.json();
|
||||||
|
if (result.error) throw new Error(result.error);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "success",
|
||||||
|
message: "Claim submitted & PDF downloaded successfully.",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Success",
|
||||||
|
description: "Claim Submitted and Pdf Downloaded completed.",
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectedPatient(null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error: any) {
|
||||||
|
dispatch(
|
||||||
|
setTaskStatus({
|
||||||
|
status: "error",
|
||||||
|
message: error.message || "Failed to download PDF",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: error.message || "Failed to fetch PDF",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-gray-100">
|
<div className="flex h-screen overflow-hidden bg-gray-100">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
@@ -565,6 +615,8 @@ export default function ClaimsPage() {
|
|||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
<TopAppBar toggleMobileMenu={toggleMobileMenu} />
|
||||||
|
|
||||||
|
<SeleniumTaskBanner />
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto p-4">
|
<main className="flex-1 overflow-y-auto p-4">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
@@ -680,31 +732,6 @@ export default function ClaimsPage() {
|
|||||||
onHandleForSelenium={handleSelenium}
|
onHandleForSelenium={handleSelenium}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isMhPopupOpen && (
|
|
||||||
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black bg-opacity-50">
|
|
||||||
<div className="bg-white rounded-lg shadow-lg p-6 max-w-md w-full">
|
|
||||||
<h2 className="text-xl font-semibold mb-4">Selenium Handler</h2>
|
|
||||||
|
|
||||||
<div className="flex justify-between space-x-4">
|
|
||||||
<Button
|
|
||||||
className="flex-1"
|
|
||||||
onClick={() => handleSeleniumPopup("submit")}
|
|
||||||
>
|
|
||||||
Download the PDF.
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="flex-1"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => setIsMhPopupOpen(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
5
apps/Frontend/src/redux/hooks.ts
Normal file
5
apps/Frontend/src/redux/hooks.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
|
import type { RootState, AppDispatch } from "./store";
|
||||||
|
|
||||||
|
export const useAppDispatch: () => AppDispatch = useDispatch;
|
||||||
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
32
apps/Frontend/src/redux/slices/seleniumTaskSlice.ts
Normal file
32
apps/Frontend/src/redux/slices/seleniumTaskSlice.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
export type TaskStatus = "idle" | "pending" | "success" | "error";
|
||||||
|
|
||||||
|
export interface SeleniumTaskState {
|
||||||
|
status: TaskStatus;
|
||||||
|
message: string;
|
||||||
|
show: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: SeleniumTaskState = {
|
||||||
|
status: "idle",
|
||||||
|
message: "",
|
||||||
|
show: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const seleniumTaskSlice = createSlice({
|
||||||
|
name: "seleniumTask",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setTaskStatus: (
|
||||||
|
state: SeleniumTaskState,
|
||||||
|
action: PayloadAction<Partial<SeleniumTaskState>>
|
||||||
|
) => {
|
||||||
|
return { ...state, ...action.payload, show: true };
|
||||||
|
},
|
||||||
|
clearTaskStatus: () => initialState,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setTaskStatus, clearTaskStatus } = seleniumTaskSlice.actions;
|
||||||
|
export default seleniumTaskSlice.reducer;
|
||||||
11
apps/Frontend/src/redux/store.ts
Normal file
11
apps/Frontend/src/redux/store.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
|
import seleniumTaskReducer from "./slices/seleniumTaskSlice";
|
||||||
|
|
||||||
|
export const store = configureStore({
|
||||||
|
reducer: {
|
||||||
|
seleniumTask: seleniumTaskReducer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
@@ -19,31 +19,14 @@ async def start_workflow(request: Request):
|
|||||||
data = await request.json()
|
data = await request.json()
|
||||||
try:
|
try:
|
||||||
bot = AutomationMassHealth(data)
|
bot = AutomationMassHealth(data)
|
||||||
result = bot.main_workflow_upto_step2("https://providers.massdhp.com/providers_login.asp")
|
result = bot.main_workflow("https://providers.massdhp.com/providers_login.asp")
|
||||||
|
|
||||||
|
if result.get("status") != "success":
|
||||||
|
return {"status": "error", "message": result.get("message")}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
# Endpoint: Step 2 — Extract the PDF content after manual submission
|
|
||||||
@app.post("/fetch-pdf")
|
|
||||||
async def fetch_pdf():
|
|
||||||
try:
|
|
||||||
bot = AutomationMassHealth.get_last_instance()
|
|
||||||
if not bot:
|
|
||||||
return {"status": "error", "message": "No running automation session"}
|
|
||||||
|
|
||||||
result = bot.reach_to_pdf()
|
|
||||||
|
|
||||||
if result.get("status") != "success":
|
|
||||||
return {"status": "error", "message": result.get("message")}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"pdf_url": result["pdf_url"]
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
return {"status": "error", "message": str(e)}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host="0.0.0.0", port=5002)
|
uvicorn.run(app, host="0.0.0.0", port=5002)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"name": "seleniumservice",
|
"name": "seleniumservice",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "pip install -r requirements.txt"
|
"postinstall": "pip install -r requirements.txt",
|
||||||
|
"dev": "python agent.py"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,11 @@ from datetime import datetime
|
|||||||
import tempfile
|
import tempfile
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
class AutomationMassHealth:
|
class AutomationMassHealth:
|
||||||
last_instance = None
|
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self.headless = False
|
self.headless = False
|
||||||
self.driver = None
|
self.driver = None
|
||||||
AutomationMassHealth.last_instance = self
|
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
self.claim = data.get("claim", {})
|
self.claim = data.get("claim", {})
|
||||||
@@ -36,11 +31,9 @@ class AutomationMassHealth:
|
|||||||
self.missingTeethStatus = self.claim.get("missingTeethStatus", "")
|
self.missingTeethStatus = self.claim.get("missingTeethStatus", "")
|
||||||
self.missingTeeth = self.claim.get("missingTeeth", {})
|
self.missingTeeth = self.claim.get("missingTeeth", {})
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_last_instance():
|
|
||||||
return AutomationMassHealth.last_instance
|
|
||||||
|
|
||||||
def config_driver(self):
|
def config_driver(self):
|
||||||
|
print("caled config")
|
||||||
options = webdriver.ChromeOptions()
|
options = webdriver.ChromeOptions()
|
||||||
if self.headless:
|
if self.headless:
|
||||||
options.add_argument("--headless")
|
options.add_argument("--headless")
|
||||||
@@ -289,13 +282,23 @@ class AutomationMassHealth:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error while filling remarks: {e}")
|
print(f"Error while filling remarks: {e}")
|
||||||
return "ERROR:REMARKS FAILED"
|
return "ERROR:REMARKS FAILED"
|
||||||
|
|
||||||
|
# 5 - close buton
|
||||||
|
try:
|
||||||
|
close_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit' and @value='Submit Request']")))
|
||||||
|
close_button.click()
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error while Closing: {e}")
|
||||||
|
return "ERROR:CLOSE FAILED"
|
||||||
|
|
||||||
return "Success"
|
return "Success"
|
||||||
|
|
||||||
|
|
||||||
def reach_to_pdf(self):
|
def reach_to_pdf(self):
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 90)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print("Waiting for PDF link to appear on success page...")
|
print("Waiting for PDF link to appear on success page...")
|
||||||
pdf_link_element = wait.until(
|
pdf_link_element = wait.until(
|
||||||
@@ -313,10 +316,7 @@ class AutomationMassHealth:
|
|||||||
full_pdf_url = pdf_relative_url
|
full_pdf_url = pdf_relative_url
|
||||||
|
|
||||||
print("FULL PDF LINK: ",full_pdf_url)
|
print("FULL PDF LINK: ",full_pdf_url)
|
||||||
return {
|
return full_pdf_url
|
||||||
"status": "success",
|
|
||||||
"pdf_url": full_pdf_url
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: {str(e)}")
|
print(f"ERROR: {str(e)}")
|
||||||
@@ -328,27 +328,37 @@ class AutomationMassHealth:
|
|||||||
finally:
|
finally:
|
||||||
if self.driver:
|
if self.driver:
|
||||||
self.driver.quit()
|
self.driver.quit()
|
||||||
AutomationMassHealth.last_instance = None
|
|
||||||
|
|
||||||
|
def main_workflow(self, url):
|
||||||
def main_workflow_upto_step2(self, url):
|
try:
|
||||||
self.config_driver()
|
self.config_driver()
|
||||||
print("Reaching Site :", url)
|
print("Reaching Site :", url)
|
||||||
self.driver.maximize_window()
|
self.driver.maximize_window()
|
||||||
self.driver.get(url)
|
self.driver.get(url)
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
if self.login().startswith("ERROR"):
|
login_result = self.login()
|
||||||
return {"status": "error", "message": "Login failed"}
|
if login_result.startswith("ERROR"):
|
||||||
|
return {"status": "error", "message": login_result}
|
||||||
|
|
||||||
if self.step1().startswith("ERROR"):
|
step1_result = self.step1()
|
||||||
return {"status": "error", "message": "Step1 failed"}
|
if step1_result.startswith("ERROR"):
|
||||||
|
return {"status": "error", "message": step1_result}
|
||||||
|
|
||||||
if self.step2().startswith("ERROR"):
|
step2_result = self.step2()
|
||||||
return {"status": "error", "message": "Step2 failed"}
|
if step2_result.startswith("ERROR"):
|
||||||
|
return {"status": "error", "message": step2_result}
|
||||||
|
|
||||||
|
reachToPdf_result = self.reach_to_pdf()
|
||||||
|
if reachToPdf_result.startswith("ERROR"):
|
||||||
|
return {"status": "error", "message": reachToPdf_result}
|
||||||
|
|
||||||
print("Waiting for user to manually submit form in browser...")
|
return {
|
||||||
return {
|
"status": "success",
|
||||||
"status": "waiting_for_user",
|
"pdf_url": reachToPdf_result
|
||||||
"message": "Automation paused. Please submit the form manually in browser."
|
}
|
||||||
}
|
except Exception as e:
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": e
|
||||||
|
}
|
||||||
|
|||||||
6
apps/SeleniumService/~/.wdm/drivers.json
Normal file
6
apps/SeleniumService/~/.wdm/drivers.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"win64_chromedriver_137.0.7151.119_for_137.0.7151": {
|
||||||
|
"timestamp": "27/06/2025",
|
||||||
|
"binary_path": "~\\.wdm\\drivers\\chromedriver\\win64\\137.0.7151.119\\chromedriver-win32/chromedriver.exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2015 The Chromium Authors
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following disclaimer
|
||||||
|
// in the documentation and/or other materials provided with the
|
||||||
|
// distribution.
|
||||||
|
// * Neither the name of Google LLC nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
145
package-lock.json
generated
145
package-lock.json
generated
@@ -13,11 +13,14 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"shx": "^0.4.0"
|
"shx": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react-redux": "^7.1.34",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"turbo": "^2.5.3"
|
"turbo": "^2.5.3"
|
||||||
@@ -3544,6 +3547,32 @@
|
|||||||
"react-dom": ">=16.8.0"
|
"react-dom": ">=16.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
|
||||||
|
"integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"@standard-schema/utils": "^0.3.0",
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@replit/vite-plugin-shadcn-theme-json": {
|
"node_modules/@replit/vite-plugin-shadcn-theme-json": {
|
||||||
"version": "0.0.4",
|
"version": "0.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@replit/vite-plugin-shadcn-theme-json/-/vite-plugin-shadcn-theme-json-0.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@replit/vite-plugin-shadcn-theme-json/-/vite-plugin-shadcn-theme-json-0.0.4.tgz",
|
||||||
@@ -3835,6 +3864,18 @@
|
|||||||
"integrity": "sha512-auUj4k+f4pyrIVf4GW5UKquSZFHJWri06QgARy9C0t9ZTjJLIuNIrr1yl9bWcJWJ1Gz1vOvYN1D+QPaIlNMVkQ==",
|
"integrity": "sha512-auUj4k+f4pyrIVf4GW5UKquSZFHJWri06QgARy9C0t9ZTjJLIuNIrr1yl9bWcJWJ1Gz1vOvYN1D+QPaIlNMVkQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.10",
|
"version": "4.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
|
||||||
@@ -4396,6 +4437,17 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
|
||||||
|
"integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/http-errors": {
|
"node_modules/@types/http-errors": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
|
||||||
@@ -4526,6 +4578,29 @@
|
|||||||
"@types/react": "^19.0.0"
|
"@types/react": "^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-redux": {
|
||||||
|
"version": "7.1.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz",
|
||||||
|
"integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.0",
|
||||||
|
"@types/react": "*",
|
||||||
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
|
"redux": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-redux/node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/retry": {
|
"node_modules/@types/retry": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||||
@@ -4576,6 +4651,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.18.1",
|
"version": "8.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||||
@@ -6296,6 +6377,15 @@
|
|||||||
"redux": "^4.2.0"
|
"redux": "^4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dnd-core/node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||||
@@ -7832,6 +7922,16 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@@ -10516,6 +10616,29 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
|
"use-sync-external-store": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^18.2.25 || ^19",
|
||||||
|
"react": "^18.0 || ^19",
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@@ -10844,12 +10967,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "4.2.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"peerDependencies": {
|
||||||
"@babel/runtime": "^7.9.2"
|
"redux": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regexparam": {
|
"node_modules/regexparam": {
|
||||||
@@ -10873,6 +11002,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"seed": "ts-node packages/db/prisma/seed.ts"
|
"seed": "ts-node packages/db/prisma/seed.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react-redux": "^7.1.34",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"turbo": "^2.5.3"
|
"turbo": "^2.5.3"
|
||||||
@@ -31,8 +32,10 @@
|
|||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"dotenv-cli": "^8.0.0",
|
"dotenv-cli": "^8.0.0",
|
||||||
|
"react-redux": "^9.2.0",
|
||||||
"shx": "^0.4.0"
|
"shx": "^0.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user