parent
9e39a1f9a1
commit
344d5792ed
|
|
@ -16,6 +16,7 @@ import ClinicDocUpdater from "../views/ClinicDocUpdate";
|
||||||
import ForgotPassword from "../views/ForgotPassword";
|
import ForgotPassword from "../views/ForgotPassword";
|
||||||
import ResetPassword from "../views/ResetPassword";
|
import ResetPassword from "../views/ResetPassword";
|
||||||
import SignupCharges from "../views/SignupCharges";
|
import SignupCharges from "../views/SignupCharges";
|
||||||
|
import PaymentSuccessPage from "../views/WaitingPage";
|
||||||
|
|
||||||
export const routesData = [
|
export const routesData = [
|
||||||
{
|
{
|
||||||
|
|
@ -51,9 +52,10 @@ export const routesData = [
|
||||||
component: ForgotPassword,
|
component: ForgotPassword,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'reset-password',
|
path: "reset-password",
|
||||||
component: ResetPassword,
|
component: ResetPassword,
|
||||||
},
|
},
|
||||||
|
{ path: "waiting", component: PaymentSuccessPage },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ export const deleteClinicOffer = (id) => {
|
||||||
export const getClinicDoctors = (params) => {
|
export const getClinicDoctors = (params) => {
|
||||||
let searchParams = new URLSearchParams();
|
let searchParams = new URLSearchParams();
|
||||||
searchParams.append("limit", params?.pagination?.pageSize ?? 10);
|
searchParams.append("limit", params?.pagination?.pageSize ?? 10);
|
||||||
searchParams.append("page", params?.pagination.pageIndex + 1 ?? 1);
|
searchParams.append("page", params?.pagination?.pageIndex + 1 ?? 1);
|
||||||
searchParams.append("search", params?.globalFilter ?? "");
|
searchParams.append("search", params?.globalFilter ?? "");
|
||||||
|
|
||||||
const url = `/clinic-doctors?${searchParams.toString()}`;
|
const url = `/clinic-doctors?${searchParams.toString()}`;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { axiosInstance } from "../config/api";
|
||||||
|
|
||||||
export const getMasterData = (params) => {
|
export const getMasterData = (params) => {
|
||||||
let searchParams = new URLSearchParams();
|
let searchParams = new URLSearchParams();
|
||||||
searchParams.append("limit", params?.pagination?.pageSize ?? 10);
|
searchParams.append("limit", 100);
|
||||||
searchParams.append("page", params?.pagination.pageIndex + 1 ?? 1);
|
searchParams.append("page", 1);
|
||||||
searchParams.append("search", params?.globalFilter ?? "");
|
searchParams.append("search", params?.globalFilter ?? "");
|
||||||
|
|
||||||
const url = `/admin/master-data?${searchParams.toString()}`;
|
const url = `/admin/master-data?${searchParams.toString()}`;
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ const ClinicDocUpdater = () => {
|
||||||
isDisabled={formik.values.isContractVerified}
|
isDisabled={formik.values.isContractVerified}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} sm={12} xs={12}>
|
{/* <Grid item md={4} sm={12} xs={12}>
|
||||||
<CustomFileUpload
|
<CustomFileUpload
|
||||||
label="Add Logo*"
|
label="Add Logo*"
|
||||||
documentName="logo"
|
documentName="logo"
|
||||||
|
|
@ -206,7 +206,7 @@ const ClinicDocUpdater = () => {
|
||||||
}
|
}
|
||||||
isDisabled={formik.values.isLogoVerified}
|
isDisabled={formik.values.isLogoVerified}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid> */}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
|
|
|
||||||
|
|
@ -229,10 +229,10 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
|
||||||
clinicPhone: number,
|
clinicPhone: number,
|
||||||
clinicPonePrefix: countryCode,
|
clinicPonePrefix: countryCode,
|
||||||
clinicAddress: clinicResp?.address,
|
clinicAddress: clinicResp?.address,
|
||||||
clinicOthers: clinicResp?.general_info,
|
clinicOthers: clinicResp?.general_info ?? "",
|
||||||
otherInfo: clinicResp?.other_info ?? formData.otherInfo,
|
otherInfo: clinicResp?.other_info ?? "",
|
||||||
clinicGreetings: clinicResp?.greeting_msg ?? formData.clinicGreetings,
|
clinicGreetings: clinicResp?.greeting_msg ?? "",
|
||||||
clinicScenarios: clinicResp?.scenarios ?? formData.clinicScenarios,
|
clinicScenarios: clinicResp?.scenarios ?? "",
|
||||||
integrationSoftware: clinicResp?.integration,
|
integrationSoftware: clinicResp?.integration,
|
||||||
practiceId: clinicResp?.pms_id,
|
practiceId: clinicResp?.pms_id,
|
||||||
practiceName: clinicResp?.practice_name,
|
practiceName: clinicResp?.practice_name,
|
||||||
|
|
@ -652,7 +652,6 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
|
||||||
className={classes.mobileNumberInput}
|
className={classes.mobileNumberInput}
|
||||||
type="string"
|
type="string"
|
||||||
style={{ width: "40%" }}
|
style={{ width: "40%" }}
|
||||||
label="Clinic Phone Number"
|
|
||||||
name="clinicPhone"
|
name="clinicPhone"
|
||||||
value={formData.clinicPhone}
|
value={formData.clinicPhone}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
|
@ -1018,7 +1017,27 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Stepper */}
|
{/* Stepper */}
|
||||||
<Stepper activeStep={activeStep} sx={{ mb: 4 }}>
|
<Stepper
|
||||||
|
activeStep={activeStep}
|
||||||
|
sx={{
|
||||||
|
mb: 4,
|
||||||
|
'& .Mui-completed': {
|
||||||
|
'& .MuiStepIcon-root': {
|
||||||
|
color: 'success.main',
|
||||||
|
},
|
||||||
|
'& .MuiStepLabel-label.Mui-completed': {
|
||||||
|
color: 'success.main',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiStepIcon-active': {
|
||||||
|
color: 'primary.main',
|
||||||
|
},
|
||||||
|
'& .MuiStepIcon-completed': {
|
||||||
|
color: 'success.main',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
{steps.map((label, index) => {
|
{steps.map((label, index) => {
|
||||||
const stepProps = {};
|
const stepProps = {};
|
||||||
const labelProps = {};
|
const labelProps = {};
|
||||||
|
|
@ -1054,9 +1073,9 @@ If No -Pt who do not have a Medicare card / Private patients with or without I
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
|
<Box sx={{ display: "flex", flexDirection: "row", pt: 2 }}>
|
||||||
<Box sx={{ flex: "1 1 auto" }} />
|
<Box sx={{ flex: "1 1 auto" }} />
|
||||||
<Button onClick={handleBack} sx={{ mr: 1 }} color="primary" variant="outlined">
|
{/* <Button onClick={handleBack} sx={{ mr: 1 }} color="primary" variant="outlined">
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button> */}
|
||||||
<Button onClick={handleSubmit} variant="contained" color="primary" type="submit" disabled={activeStep == 4 && !testConnDone}>
|
<Button onClick={handleSubmit} variant="contained" color="primary" type="submit" disabled={activeStep == 4 && !testConnDone}>
|
||||||
Submit Setup
|
Submit Setup
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -84,18 +84,13 @@ const ClinicsList = () => {
|
||||||
{
|
{
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
size: "220px",
|
size: 100,
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
header: "Clinic Name",
|
header: "Clinic Name",
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
<>
|
<>
|
||||||
<div className={classes.companyNameTableColumn}>
|
<div>
|
||||||
{/* <img
|
<div>
|
||||||
className={classes.companyNameLogo}
|
|
||||||
src="https://dev-ai-appointment.s3.ap-southeast-2.amazonaws.com/common/clinic_logo.jpg"
|
|
||||||
alt="logo"
|
|
||||||
/> */}
|
|
||||||
<div className={classes.companyName}>
|
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/clinics/${row?.original?.id}`,
|
pathname: `/clinics/${row?.original?.id}`,
|
||||||
|
|
@ -105,10 +100,6 @@ const ClinicsList = () => {
|
||||||
>
|
>
|
||||||
<>{row?.original.name}</>
|
<>{row?.original.name}</>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className={classes.companyWebsiteLabel}>
|
|
||||||
{row.original.website}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
@ -118,7 +109,7 @@ const ClinicsList = () => {
|
||||||
{
|
{
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
size: 100,
|
size: 150,
|
||||||
accessorKey: "update_time",
|
accessorKey: "update_time",
|
||||||
header: "Req.Raised On",
|
header: "Req.Raised On",
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
|
|
@ -132,43 +123,43 @@ const ClinicsList = () => {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// .................send email column..................
|
// .................send email column..................
|
||||||
{
|
// {
|
||||||
enableColumnFilter: false,
|
// enableColumnFilter: false,
|
||||||
enableSorting: true,
|
// enableSorting: true,
|
||||||
accessorKey: "lastEmailSent",
|
// accessorKey: "lastEmailSent",
|
||||||
size: 190,
|
// size: 190,
|
||||||
header: "Send Mail",
|
// header: "Send Mail",
|
||||||
Cell: ({ row }) => (
|
// Cell: ({ row }) => (
|
||||||
<>
|
// <>
|
||||||
{(row.original.status === CLINIC_STATUS.REJECTED ||
|
// {(row.original.status === CLINIC_STATUS.REJECTED ||
|
||||||
row.original.status === CLINIC_STATUS.ON_HOLD) && (
|
// row.original.status === CLINIC_STATUS.ON_HOLD) && (
|
||||||
<div>
|
// <div>
|
||||||
<Link
|
// <Link
|
||||||
onClick={() => handleEmailSent(row)}
|
// onClick={() => handleEmailSent(row)}
|
||||||
className={classes.sendEmailStatus}
|
// className={classes.sendEmailStatus}
|
||||||
>
|
// >
|
||||||
<u>
|
// <u>
|
||||||
{row.original.status === CLINIC_STATUS.ON_HOLD &&
|
// {row.original.status === CLINIC_STATUS.ON_HOLD &&
|
||||||
"Resend On Hold Mail"}
|
// "Resend On Hold Mail"}
|
||||||
{row.original.status === CLINIC_STATUS.REJECTED &&
|
// {row.original.status === CLINIC_STATUS.REJECTED &&
|
||||||
"Resend Rejection Mail"}
|
// "Resend Rejection Mail"}
|
||||||
</u>
|
// </u>
|
||||||
</Link>
|
// </Link>
|
||||||
<div className={classes.sendEmailLastSentMailDate}>
|
// <div className={classes.sendEmailLastSentMailDate}>
|
||||||
Last Sent On:
|
// Last Sent On:
|
||||||
{format(
|
// {format(
|
||||||
new Date(row?.original?.lastEmailSent),
|
// new Date(row?.original?.lastEmailSent),
|
||||||
"dd MMM yyyy"
|
// "dd MMM yyyy"
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
)}
|
// )}
|
||||||
</>
|
// </>
|
||||||
),
|
// ),
|
||||||
},
|
// },
|
||||||
// .................location column..................
|
// .................location column..................
|
||||||
{
|
{
|
||||||
size: 100,
|
size: 150,
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
accessorKey: "city",
|
accessorKey: "city",
|
||||||
|
|
@ -196,7 +187,7 @@ const ClinicsList = () => {
|
||||||
accessorFn: ({ status }) => (
|
accessorFn: ({ status }) => (
|
||||||
<>
|
<>
|
||||||
<Chip
|
<Chip
|
||||||
className={classes.chipClass}
|
// className={classes.chipClass}
|
||||||
label={
|
label={
|
||||||
status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase() ? (
|
status === CLINIC_STATUS.UNDER_REVIEW.toLowerCase() ? (
|
||||||
"Under Review"
|
"Under Review"
|
||||||
|
|
@ -275,14 +266,8 @@ const ClinicsList = () => {
|
||||||
header: "Clinic Name",
|
header: "Clinic Name",
|
||||||
Cell: ({ row }) => (
|
Cell: ({ row }) => (
|
||||||
<>
|
<>
|
||||||
<div className={classes.companyNameTableColumn}>
|
<div>
|
||||||
{/* <img
|
<div>
|
||||||
className={classes.companyNameLogo}
|
|
||||||
src="https://dev-ai-appointment.s3.ap-southeast-2.amazonaws.com/common/clinic_logo.jpg"
|
|
||||||
alt="logo"
|
|
||||||
/> */}
|
|
||||||
|
|
||||||
<div className={classes.companyName}>
|
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/clinics/${row?.original?.id}`,
|
pathname: `/clinics/${row?.original?.id}`,
|
||||||
|
|
@ -292,9 +277,6 @@ const ClinicsList = () => {
|
||||||
>
|
>
|
||||||
<>{row?.original.name}</>
|
<>{row?.original.name}</>
|
||||||
</Link>
|
</Link>
|
||||||
<div className={classes.companyWebsiteLabel}>
|
|
||||||
{row.original.website}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -146,13 +146,13 @@ const PaymentConfig = () => {
|
||||||
<Grid item xs={12} md={4}>
|
<Grid item xs={12} md={4}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Per Minute Charges"
|
label="Per Call Charges"
|
||||||
name="per_call_charges"
|
name="per_call_charges"
|
||||||
value={paymentConfig.per_call_charges}
|
value={paymentConfig.per_call_charges}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
startAdornment: <InputAdornment position="start">$</InputAdornment>,
|
startAdornment: <InputAdornment position="start">$</InputAdornment>,
|
||||||
endAdornment: <InputAdornment position="end">/min</InputAdornment>,
|
endAdornment: <InputAdornment position="end">/calls</InputAdornment>,
|
||||||
}}
|
}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ const MasterDataManagement = () => {
|
||||||
|
|
||||||
const handleEdit = async (row) => {
|
const handleEdit = async (row) => {
|
||||||
setEditId(row.original.id); // Store the ID for update operation
|
setEditId(row.original.id); // Store the ID for update operation
|
||||||
setType(row.original.type);
|
setType(row.original.type.charAt(0).toUpperCase() + row.original.type.slice(1));
|
||||||
setOpenDialog(true);
|
setOpenDialog(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,10 @@ function YourDetailsForm() {
|
||||||
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
|
pushNotification(response?.data?.message, NOTIFICATION.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pushNotification("OTP sent successfully to your email", NOTIFICATION.SUCCESS);
|
pushNotification(
|
||||||
|
"OTP sent successfully to your email",
|
||||||
|
NOTIFICATION.SUCCESS
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const verifyOTP = async (e) => {
|
const verifyOTP = async (e) => {
|
||||||
|
|
@ -393,8 +396,9 @@ function YourDetailsForm() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
|
pushNotification(response?.data?.message, NOTIFICATION.SUCCESS);
|
||||||
|
localStorage.setItem("temp_signup_token", response?.data?.data?.token);
|
||||||
// open new page for stripe payment from url
|
// open new page for stripe payment from url
|
||||||
const newWindow = window.open(response?.data?.data, "_blank", "noopener,noreferrer");
|
window.open(response?.data?.data?.url, "_blank", "noopener,noreferrer");
|
||||||
// if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
|
// if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
|
||||||
// // Fallback in case popup is blocked
|
// // Fallback in case popup is blocked
|
||||||
// window.location.href = response?.data?.data;
|
// window.location.href = response?.data?.data;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,436 @@
|
||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Avatar,
|
||||||
|
LinearProgress,
|
||||||
|
Chip,
|
||||||
|
Stack,
|
||||||
|
Paper,
|
||||||
|
Divider,
|
||||||
|
Alert,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
CheckCircle,
|
||||||
|
Schedule,
|
||||||
|
ArrowBack,
|
||||||
|
OpenInNew,
|
||||||
|
Error,
|
||||||
|
Refresh,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { green, blue, red, grey } from "@mui/material/colors";
|
||||||
|
import { API_BASE_URL } from "../../common/envVariables";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const PaymentSuccessPage = () => {
|
||||||
|
const [status, setStatus] = useState("processing"); // 'processing', 'success', 'error'
|
||||||
|
const [invoiceUrl, setInvoiceUrl] = useState("");
|
||||||
|
const [timeElapsed, setTimeElapsed] = useState(0);
|
||||||
|
const [token, setToken] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Use refs to track active polling and timer
|
||||||
|
const pollingRef = useRef(null);
|
||||||
|
const timerRef = useRef(null);
|
||||||
|
const isPollingActiveRef = useRef(false);
|
||||||
|
|
||||||
|
const checkPaymentStatus = async () => {
|
||||||
|
try {
|
||||||
|
if (!token || !isPollingActiveRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/stripe/get-invoice`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.data && isPollingActiveRef.current) {
|
||||||
|
// Success - stop polling and timer
|
||||||
|
isPollingActiveRef.current = false;
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearTimeout(pollingRef.current);
|
||||||
|
pollingRef.current = null;
|
||||||
|
}
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearInterval(timerRef.current);
|
||||||
|
timerRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("success");
|
||||||
|
setInvoiceUrl(data.data);
|
||||||
|
} else if (isPollingActiveRef.current) {
|
||||||
|
// Still processing, check again in 10 seconds
|
||||||
|
pollingRef.current = setTimeout(() => checkPaymentStatus(), 10000);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking payment status:", error);
|
||||||
|
if (isPollingActiveRef.current) {
|
||||||
|
// Retry after 10 seconds on error
|
||||||
|
pollingRef.current = setTimeout(() => checkPaymentStatus(), 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startTimer = () => {
|
||||||
|
timerRef.current = setInterval(() => {
|
||||||
|
setTimeElapsed(prev => prev + 1);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopPollingAndTimer = () => {
|
||||||
|
isPollingActiveRef.current = false;
|
||||||
|
|
||||||
|
if (pollingRef.current) {
|
||||||
|
clearTimeout(pollingRef.current);
|
||||||
|
pollingRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearInterval(timerRef.current);
|
||||||
|
timerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openInvoice = () => {
|
||||||
|
// Stop all polling and timer before navigation
|
||||||
|
stopPollingAndTimer();
|
||||||
|
|
||||||
|
if (invoiceUrl) {
|
||||||
|
window.open(invoiceUrl, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up and navigate
|
||||||
|
localStorage.removeItem("temp_signup_token");
|
||||||
|
setToken("");
|
||||||
|
navigate("/auth/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToLogin = () => {
|
||||||
|
stopPollingAndTimer();
|
||||||
|
localStorage.removeItem("temp_signup_token");
|
||||||
|
navigate("/auth/login");
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToDashboard = () => {
|
||||||
|
stopPollingAndTimer();
|
||||||
|
navigate("/dashboard");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRetry = () => {
|
||||||
|
stopPollingAndTimer();
|
||||||
|
setStatus("processing");
|
||||||
|
setTimeElapsed(0);
|
||||||
|
isPollingActiveRef.current = true;
|
||||||
|
startTimer();
|
||||||
|
checkPaymentStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (seconds) => {
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const storedToken = localStorage.getItem("temp_signup_token");
|
||||||
|
if (!storedToken) {
|
||||||
|
navigate("/auth/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setToken(storedToken);
|
||||||
|
isPollingActiveRef.current = true;
|
||||||
|
startTimer();
|
||||||
|
|
||||||
|
// Start checking payment status once token is set
|
||||||
|
const initializeChecking = async () => {
|
||||||
|
if (storedToken) {
|
||||||
|
checkPaymentStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializeChecking();
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
return () => {
|
||||||
|
stopPollingAndTimer();
|
||||||
|
};
|
||||||
|
}, []); // Remove token from dependency array to avoid infinite loop
|
||||||
|
|
||||||
|
// Separate useEffect for when token changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (token && status === "processing" && !isPollingActiveRef.current) {
|
||||||
|
isPollingActiveRef.current = true;
|
||||||
|
checkPaymentStatus();
|
||||||
|
}
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
if (status === "processing") {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
background: "linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card sx={{ maxWidth: 400, width: "100%", textAlign: "center" }}>
|
||||||
|
<CardContent sx={{ p: 4 }}>
|
||||||
|
{/* Header */}
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
bgcolor: blue[100],
|
||||||
|
color: blue[600],
|
||||||
|
mx: "auto",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Schedule />
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<Typography variant="h5" fontWeight="bold" gutterBottom>
|
||||||
|
Processing Your Payment
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
Please wait while we confirm your subscription and prepare your
|
||||||
|
invoice...
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Loading Animation */}
|
||||||
|
<Box sx={{ mb: 3 }}>
|
||||||
|
<CircularProgress size={40} sx={{ mb: 2 }} />
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Time elapsed: {formatTime(timeElapsed)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Progress Steps */}
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckCircle
|
||||||
|
sx={{ color: green[500], mr: 1, fontSize: 18 }}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" sx={{ color: green[600] }}>
|
||||||
|
Payment Submitted
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress
|
||||||
|
size={16}
|
||||||
|
sx={{ mr: 1, color: blue[500] }}
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" sx={{ color: blue[600] }}>
|
||||||
|
Confirming Payment
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Schedule sx={{ color: grey[400], mr: 1, fontSize: 18 }} />
|
||||||
|
<Typography variant="body2" color="text.disabled">
|
||||||
|
Generating Invoice
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Back Button */}
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ArrowBack />}
|
||||||
|
onClick={goToLogin}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
>
|
||||||
|
Back to Login
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "success") {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
background: "linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card sx={{ maxWidth: 400, width: "100%", textAlign: "center" }}>
|
||||||
|
<CardContent sx={{ p: 4 }}>
|
||||||
|
{/* Success Header */}
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
bgcolor: green[100],
|
||||||
|
color: green[600],
|
||||||
|
mx: "auto",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CheckCircle />
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<Typography variant="h5" fontWeight="bold" gutterBottom>
|
||||||
|
Payment Successful!
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
Your subscription has been activated and your invoice is ready.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{/* Processing Time */}
|
||||||
|
<Chip
|
||||||
|
label={`Processed in ${formatTime(timeElapsed)}`}
|
||||||
|
color="success"
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ mb: 3 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<OpenInNew />}
|
||||||
|
onClick={openInvoice}
|
||||||
|
sx={{ py: 1.5 }}
|
||||||
|
>
|
||||||
|
View Invoice
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
onClick={goToDashboard}
|
||||||
|
sx={{ py: 1.5 }}
|
||||||
|
>
|
||||||
|
Continue to Dashboard
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 1 }} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
color="inherit"
|
||||||
|
startIcon={<ArrowBack />}
|
||||||
|
onClick={goToLogin}
|
||||||
|
>
|
||||||
|
Back to Login
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "error") {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
minHeight: "100vh",
|
||||||
|
background: "linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
p: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card sx={{ maxWidth: 400, width: "100%", textAlign: "center" }}>
|
||||||
|
<CardContent sx={{ p: 4 }}>
|
||||||
|
{/* Error Header */}
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
bgcolor: red[100],
|
||||||
|
color: red[600],
|
||||||
|
mx: "auto",
|
||||||
|
mb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Error />
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
<Typography variant="h5" fontWeight="bold" gutterBottom>
|
||||||
|
Payment Processing Error
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Alert severity="error" sx={{ mb: 3, textAlign: "left" }}>
|
||||||
|
We encountered an issue while processing your payment. Please try
|
||||||
|
again or contact support.
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<Refresh />}
|
||||||
|
onClick={handleRetry}
|
||||||
|
sx={{ py: 1.5 }}
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
startIcon={<ArrowBack />}
|
||||||
|
onClick={goToLogin}
|
||||||
|
sx={{ py: 1.5 }}
|
||||||
|
>
|
||||||
|
Back to Login
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaymentSuccessPage;
|
||||||
Loading…
Reference in New Issue