initially setupuser can update or revnew plan

This commit is contained in:
pramod.mahajan 2025-10-30 00:18:37 +05:30
parent d81ffe86b7
commit 7b4d19a932
7 changed files with 376 additions and 159 deletions

View File

@ -145,7 +145,7 @@
} }
.text-xxs { font-size: 0.55rem; } /* 8px */ .text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */ .text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */ .text-sm { font-size: 0.675rem; } /* 14px */
.text-base { font-size: 1rem; } /* 16px */ .text-base { font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */ .text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */ .text-xl { font-size: 1.25rem; } /* 20px */

View File

@ -1,12 +1,12 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { VIEW_DOCUMENT } from "../../utils/constants"; import { SUPPER_TENANT, VIEW_DOCUMENT } from "../../utils/constants";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
const EmployeeNav = ({ onPillClick, activePill }) => { const EmployeeNav = ({ onPillClick, activePill }) => {
const { employeeId } = useParams(); const { employeeId } = useParams();
const [isAbleToViewDocuments, setIsAbleToViewDocuments] = useState(false); const [isAbleToViewDocuments, setIsAbleToViewDocuments] = useState(false);
const isSuperUser = useHasUserPermission(SUPPER_TENANT)
const canViewDocuments = useHasUserPermission(VIEW_DOCUMENT); const canViewDocuments = useHasUserPermission(VIEW_DOCUMENT);
const { profile } = useProfile(); const { profile } = useProfile();
@ -24,6 +24,7 @@ const EmployeeNav = ({ onPillClick, activePill }) => {
icon: "bx bx-file", icon: "bx bx-file",
label: "Documents", label: "Documents",
}, },
(isSuperUser && { key: "setting", icon: "bx bx-cog", label: "Setting" }),
// { key: "activities", icon: "bx bx-grid-alt", label: "Activities" }, // { key: "activities", icon: "bx bx-grid-alt", label: "Activities" },
].filter(Boolean); ].filter(Boolean);
return ( return (

View File

@ -0,0 +1,51 @@
import React, { useState } from "react";
import SubscriptionOverview from "./SubscriptionOverview";
const ManageSubscription = () => {
const [activeTab, setActiveTab] = useState("plan");
const renderContent = () => {
switch (activeTab) {
case "plan":
return <SubscriptionOverview/>
case "billing":
return <div className="p-3">💳 <strong>Billing Info</strong> section content goes here.</div>;
default:
return null;
}
};
return (
<div className="card page-min-h">
<div className=" border-bottom-0 pb-0 overflow-auto p-2">
<ul className="nav nav-tabs">
<li className="nav-item text-sm">
<small
className={` nav-link ${activeTab === "plan" ? "active" : ""}`}
onClick={() => setActiveTab("plan")}
>
Subscription OverView
</small>
</li>
<li className="nav-item">
<small
className={`nav-link ${activeTab === "billing" ? "active" : ""}`}
onClick={() => setActiveTab("billing")}
>
Manage Subscription
</small>
</li>
</ul>
</div>
<div className="px-2 py-1">
{renderContent()}
</div>
</div>
);
};
export default ManageSubscription;

View File

@ -0,0 +1,87 @@
import React from 'react'
import Avatar from '../../common/Avatar'
const SubscriptionOverview = () => {
return (
<div className='container-md text-start'>
<div className='row d-flex justify-content-between py-1'>
<small className='fw-semibold fs-sm-6 fs-md-5'>Overview</small>
<p className='text-muted m-0'>Summerizees all your payments and subscription for the purchsed Application</p>
<hr className="divider border-2 my-2" />
<div className='col-md-4 d-flex flex-column gap-6'>
<div className=''>
<p className=' d-bolck fw-semibold fs-6'>Contact Details</p>
<small className='d-bolck'>Who should we contact if neccessary ?</small>
</div>
<p className='fw-semibold text-primary'>Manage Contact Details</p>
</div>
<div className='col-md-6 '>
<div className='row border'>
<div className='col-6'>
<div className='d-flex flex-row gap-2 align-items-center p-2'>
<Avatar size='md' firstName={"pramod"} lastName={"Mahajan"}/>
<div >
<small className='d-block fw-semibold fs-6'>Pramod Mahajan</small>
<small className='text-muted text-tiny'>pramod.mahajan@marcoaiot.com</small>
<p className='my-3'>Marco Aiot Pvt Limited</p>
</div>
<div className="vr border-2"></div>
</div>
</div>
<div className='col-6 text-center align-items-center my-auto'>
<a className='text-primary'>Update Details</a>
</div>
</div>
</div>
</div>
<hr className="divider border-2 my-2" />
<div className='row d-flex justify-content-between py-1'>
<div className='col-md-4 d-flex flex-column gap-6'>
<div className=''>
<p className=' d-bolck fw-semibold fs-6'>Current Plan</p>
<small className='d-bolck'>You can update your plan anytime for best benifit from the product and track your projct</small>
</div>
<p className='fw-semibold text-primary'>Switch Plan</p>
</div>
<div className='col-md-6 '>
<div className='row border bg-light-primary p-2'>
<div className='d-flex justify-content-between align-items-center'>
<div className='d-flex flex-row gap-4'>
<small><i className='bx bxs-package'></i></small>
<small className='fw-bold'>Super Package</small>
<small className='fw-bold text-primary'> 5999</small>
</div>
<span className=' bg-primary rounded-circle'><i className='bx bx-md bx-check text-white'></i></span>
</div>
<div className='mt-3'>
<p className='m-0 ms-6 text-muted text-tiny'>Includes up to 100 userss, 10GB individual cloud storage and access maximum features</p>
</div>
</div>
</div>
</div>
<hr className="divider border-2 my-2" />
<div className='row d-flex justify-content-between py-1'>
<div className='col-md-4 d-flex flex-column gap-6'>
<div className=''>
<p className=' d-bolck fw-semibold fs-6'>Billing History</p>
<small className='d-bolck'>Sumary on the payment history for the subscription plan of the application</small>
</div>
<p className='fw-semibold text-primary'>Billing History</p>
</div>
<div className='col-md-6 '>
</div>
</div>
</div>
)
}
export default SubscriptionOverview

View File

@ -23,11 +23,13 @@ const ProcessedPayment = ({
setStepStatus, setStepStatus,
resetFormStep, resetFormStep,
}) => { }) => {
const { planName } = useParams(); const { planName } = useParams();
const { details: client, planId: selectedPlanId,frequency } = useSelector( const {
(store) => store.localVariables.selfTenant details: client,
); planId: selectedPlanId,
frequency,
} = useSelector((store) => store.localVariables.selfTenant);
const [selectedPlan, setSelectedPlan] = useState(null); const [selectedPlan, setSelectedPlan] = useState(null);
const [currentPlan, setCurrentPlan] = useState(null); const [currentPlan, setCurrentPlan] = useState(null);
const [failPayment, setFailPayment] = useState(null); const [failPayment, setFailPayment] = useState(null);
@ -35,7 +37,7 @@ const ProcessedPayment = ({
const { const {
data: plans, data: plans,
isError: isPlanError, isError: isPlanError,
isLoading, isLoading,isError,isRefetching,refetch
} = useSubscription(frequency); } = useSubscription(frequency);
useEffect(() => { useEffect(() => {
if (!plans || !selectedPlanId) return; if (!plans || !selectedPlanId) return;
@ -54,12 +56,10 @@ const ProcessedPayment = ({
const { mutate: MakePayment, isPending } = useMakePayment( const { mutate: MakePayment, isPending } = useMakePayment(
(response) => { (response) => {
unblockUI(); unblockUI();
onNext(response); onNext(response);
}, },
(fail) => { (fail) => {
unblockUI(); unblockUI();
setFailPayment(fail); setFailPayment(fail);
onNext(fail); onNext(fail);
@ -77,18 +77,18 @@ const ProcessedPayment = ({
alert("Failed to load Razorpay SDK"); alert("Failed to load Razorpay SDK");
return; return;
} }
MakePayment({ amount:selectedPlan?.price }); MakePayment({ amount: selectedPlan?.price });
}; };
const handleRetry = () => { const handleRetry = () => {
setFailPayment(null); setFailPayment(null);
if (typeof resetPaymentStep === "function") resetPaymentStep(); if (typeof resetPaymentStep === "function") resetPaymentStep();
}; };
const handlPrevious=()=>{ const handlPrevious = () => {
setCurrentStep, setCurrentStep,
setStepStatus((prev) => ({ ...prev, 2: "pending",3:"pending" })); setStepStatus((prev) => ({ ...prev, 2: "pending", 3: "pending" }));
setCurrentStep(2); setCurrentStep(2);
} };
// useEffect(() => { // useEffect(() => {
// if (!client || Object.keys(client).length === 0) { // if (!client || Object.keys(client).length === 0) {
@ -154,156 +154,190 @@ const ProcessedPayment = ({
and help you maximize productivity. and help you maximize productivity.
</p> </p>
</div> </div>
{isError && (
{isLoading ? <SelectedPlanSkeleton/> : (<> <div className="col-12 col-md text-center">
<p className="text-muted">{error?.message}</p>
{selectedPlan && ( <small>{error?.name}</small>
<div className="col-12 col-md-8 mb-md-3 mb-2"> <small
<div className="custom-option custom-option-basic text-start w-100 bg-light-primary border border-primary shadow-md p-3 rounded-3"> className={`text-muted ${
<div className="custom-option-header d-flex justify-content-between align-items-center"> isRefetching ? "cursor-notallowed" : "cursor-pointer"
<span className="h6 mb-0">{selectedPlan?.planName}</span> }`}
<i className="bx bx-check-circle text-primary fs-4"></i> onClick={refetch}
</div> >
{isRefetching ? (
<div className="d-flex justify-content-between mt-1"> <>
<small className="text-muted"> <i
{selectedPlan?.currency?.symbol} {selectedPlan?.price} / className={`bx bx-loader-alt ${
{frequencyLabel(frequency,false)} isRefetching ? "bx-spin" : ""
</small> }`}
</div> ></i>{" "}
Retrying...
<span className="custom-option-body d-block mt-1"> </>
<small>{selectedPlan?.description}</small> ) : (
</span> "Try to refetch"
</div> )}
</small>
</div> </div>
)} )}
{isLoading ? (
<SelectedPlanSkeleton />
) : (
<>
{selectedPlan && (
<div className="col-12 col-md-8 mb-md-3 mb-2">
<div className="custom-option custom-option-basic text-start w-100 bg-light-primary border border-primary shadow-md p-3 rounded-3">
<div className="custom-option-header d-flex justify-content-between align-items-center">
<span className="h6 mb-0">
{selectedPlan?.planName}
</span>
<i className="bx bx-check-circle text-primary fs-4"></i>
</div>
{selectedPlan && ( <div className="d-flex justify-content-between mt-1">
<div className="col-12 text-start"> <small className="text-muted">
<div className="border-warning mt-3"> {selectedPlan?.currency?.symbol} {selectedPlan?.price}{" "}
{(() => { /{frequencyLabel(frequency, false)}
const { </small>
planName, </div>
description,
price,
frequency,
trialDays,
maxUser,
maxStorage,
currency,
features,
} = selectedPlan;
return (
<>
<div className="row g-2 mb-3">
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-user me-1 text-primary"></i>
<strong>Max Users:</strong> {maxUser}
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-hdd me-1 text-primary"></i>
<strong>Max Storage:</strong> {maxStorage} MB
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-time-five me-1 text-primary"></i>
<strong>Trial Days:</strong> {trialDays}
</div>
</div>
</div>
<h6 className="fw-bold text-secondary mb-2"> <span className="custom-option-body d-block mt-1">
Included Features <small>{selectedPlan?.description}</small>
</h6> </span>
<div className="row "> </div>
{features && </div>
Object.entries(features?.modules || {}) )}
.filter(([key]) => key !== "id")
.map(([key, mod]) => ( {selectedPlan && (
<div <div className="col-12 text-start">
key={key} <div className="border-warning mt-3">
className="col-4 mb-2 d-flex align-items-center mb-1" {(() => {
> const {
<i planName,
className={`fa-regular ${ description,
mod.enabled price,
? "fa-circle-check text-success" frequency,
: "fa-circle-xmark text-danger" trialDays,
}`} maxUser,
></i> maxStorage,
<small className="ms-1">{mod.name}</small> currency,
features,
} = selectedPlan;
return (
<>
<div className="row g-2 mb-3">
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-user me-1 text-primary"></i>
<strong>Max Users:</strong> {maxUser}
</div> </div>
))} </div>
</div> <div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-hdd me-1 text-primary"></i>
<strong>Max Storage:</strong> {maxStorage} MB
</div>
</div>
<div className="col-sm-6 col-md-4">
<div className="border rounded-3 p-2 bg-light">
<i className="bx bx-time-five me-1 text-primary"></i>
<strong>Trial Days:</strong> {trialDays}
</div>
</div>
</div>
<h6 className="fw-bold text-secondary mt-3 mb-1"> <h6 className="fw-bold text-secondary mb-2">
Support Included Features
</h6> </h6>
<ul className="list-unstyled d-flex flex-wrap gap-3 align-items-center mb-0 small"> <div className="row ">
{features?.supports?.emailSupport && ( {features &&
<li className="d-flex align-items-center"> Object.entries(features?.modules || {})
<i className="bx bx-envelope text-primary me-1 fs-5"></i> .filter(([key]) => key !== "id")
Email Support .map(([key, mod]) => (
</li> <div
)} key={key}
{features?.supports?.phoneSupport && ( className="col-4 mb-2 d-flex align-items-center mb-1"
<li className="d-flex align-items-center"> >
<i className="bx bx-phone text-primary me-1 fs-5"></i> <i
Phone Support className={`fa-regular ${
</li> mod.enabled
)} ? "fa-circle-check text-success"
{features?.supports?.prioritySupport && ( : "fa-circle-xmark text-danger"
<li className="d-flex align-items-center"> }`}
<i className="bx bx-star text-warning me-1 fs-5"></i> ></i>
Priority Support <small className="ms-1">{mod.name}</small>
</li> </div>
)} ))}
</ul> </div>
<hr className="divider border-2 " />
<div className="d-flex flex-column co-12 ">
<div className="d-flex justify-content-between">
<h6 className="fs-6 m-0">Duration</h6>
<h5 className="fs-6 m-0">
{frequencyLabel(selectedPlan?.frequency, true)}
</h5>
</div>
<div className="d-flex justify-content-between"> <h6 className="fw-bold text-secondary mt-3 mb-1">
<h6 className="fs-4">Total Price</h6> Support
<h5 className="fs-3"> </h6>
{formatFigure(selectedPlan?.price, { <ul className="list-unstyled d-flex flex-wrap gap-3 align-items-center mb-0 small">
type: "currency", {features?.supports?.emailSupport && (
currency: selectedPlan?.currency.currencyCode, <li className="d-flex align-items-center">
})} <i className="bx bx-envelope text-primary me-1 fs-5"></i>
</h5> Email Support
</div> </li>
</div> )}
</> {features?.supports?.phoneSupport && (
); <li className="d-flex align-items-center">
})()} <i className="bx bx-phone text-primary me-1 fs-5"></i>
</div> Phone Support
</div> </li>
)}
{features?.supports?.prioritySupport && (
<li className="d-flex align-items-center">
<i className="bx bx-star text-warning me-1 fs-5"></i>
Priority Support
</li>
)}
</ul>
<hr className="divider border-2 " />
<div className="d-flex flex-column co-12 ">
<div className="d-flex justify-content-between">
<h6 className="fs-6 m-0">Duration</h6>
<h5 className="fs-6 m-0">
{frequencyLabel(
selectedPlan?.frequency,
true
)}
</h5>
</div>
<div className="d-flex justify-content-between">
<h6 className="fs-4">Total Price</h6>
<h5 className="fs-3">
{formatFigure(selectedPlan?.price, {
type: "currency",
currency:
selectedPlan?.currency.currencyCode,
})}
</h5>
</div>
</div>
</>
);
})()}
</div>
</div>
)}
</>
)} )}
</>)}
</div> </div>
</div> </div>
<div className="col-12 col-md-4 "> <div className="col-12 col-md-4 ">
{client && ( {client && (
<div className="text-start"> <div className="text-start">
<h6 className="fs-md-4 my-4">Confirm your organization details.</h6> <h6 className="fs-md-4 my-4">
Confirm your organization details.
</h6>
<div className="row g-2"> <div className="row g-2">
<div className="col-sm-6 mb-2"> <div className="col-sm-6 mb-2">
<strong>Name:</strong> <strong>Name:</strong>
</div> </div>
<div className="col-sm-6 mb-2">{client.firstName} {client.lastName}</div> <div className="col-sm-6 mb-2">
{client.firstName} {client.lastName}
</div>
<div className="col-sm-6"> <div className="col-sm-6">
<strong>Email:</strong> <strong>Email:</strong>
@ -320,8 +354,6 @@ const ProcessedPayment = ({
</div> </div>
<div className="col-sm-6 mb-2">{client.organizationName}</div> <div className="col-sm-6 mb-2">{client.organizationName}</div>
<div className="col-sm-6 mb-2"> <div className="col-sm-6 mb-2">
<strong>Onboarding Date:</strong> <strong>Onboarding Date:</strong>
</div> </div>
@ -344,19 +376,26 @@ const ProcessedPayment = ({
</div> </div>
</div> </div>
<div className="col-12 d-flex justify-content-between"> <div className="col-12 d-flex justify-content-between">
<button <button
type="submit" type="submit"
className="btn btn-label-primary d-flex align-items-center me-2" className="btn btn-label-primary d-flex align-items-center me-2"
onClick={handlPrevious} onClick={handlPrevious}
> >
<i className="bx bx-chevron-left"></i> Previous <i className="bx bx-chevron-left"></i> Previous
</button> </button>
<button <button
type="submit" type="submit"
className="btn btn-label-primary d-flex align-items-center me-2" className="btn btn-label-primary d-flex align-items-center me-2"
onClick={() => ProcessToPayment(currentPlan?.price)} onClick={() => ProcessToPayment(currentPlan?.price)}
> >
{isPending ? <span><i className='bx bx-loader-alt bx-md bx-spin me-2'></i>Please Wait...</span> : "Processed To Payment"} {isPending ? (
<span>
<i className="bx bx-loader-alt bx-md bx-spin me-2"></i>Please
Wait...
</span>
) : (
"Processed To Payment"
)}
</button> </button>
</div> </div>
</div> </div>

View File

@ -5,6 +5,7 @@ import { useSubscription } from "../../hooks/useAuth";
import { formatFigure, frequencyLabel } from "../../utils/appUtils"; import { formatFigure, frequencyLabel } from "../../utils/appUtils";
import { setSelfTenant } from "../../slices/localVariablesSlice"; import { setSelfTenant } from "../../slices/localVariablesSlice";
import SelectedPlanSkeleton from "./SelectedPlanSkeleton"; import SelectedPlanSkeleton from "./SelectedPlanSkeleton";
import { error } from "pdf-lib";
const SelectPlan = ({ currentStep, setStepStatus, onNext }) => { const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
const { frequency, planName } = useParams(); const { frequency, planName } = useParams();
@ -22,8 +23,11 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
const { const {
data: plans, data: plans,
isError: isPlanError, isError,
isLoading, isLoading,
error,
refetch,
isRefetching,
} = useSubscription(selectedFrequency); } = useSubscription(selectedFrequency);
const handleChange = (e) => setSelectedPlan(e.target.value); const handleChange = (e) => setSelectedPlan(e.target.value);
@ -137,6 +141,32 @@ const SelectPlan = ({ currentStep, setStepStatus, onNext }) => {
</div> </div>
</div> </div>
{isError && (
<div className="col-12 col-md text-center">
<p className="text-muted">{error?.message}</p>
<small>{error?.name}</small>
<small
className={`text-muted ${
isRefetching ? "cursor-notallowed" : "cursor-pointer"
}`}
onClick={refetch}
>
{isRefetching ? (
<>
<i
className={`bx bx-loader-alt ${
isRefetching ? "bx-spin" : ""
}`}
></i>{" "}
Retrying...
</>
) : (
"Try to refetch"
)}
</small>
</div>
)}
{isLoading ? ( {isLoading ? (
<SelectedPlanSkeleton /> <SelectedPlanSkeleton />
) : ( ) : (

View File

@ -24,6 +24,7 @@ import EmpDashboard from "../../components/Employee/EmpDashboard";
import EmpDocuments from "../../components/Employee/EmpDocuments"; import EmpDocuments from "../../components/Employee/EmpDocuments";
import EmpActivities from "../../components/Employee/EmpActivities"; import EmpActivities from "../../components/Employee/EmpActivities";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import ManageSubscription from "../../components/UserSubscription/ManageSubscription/ManageSubscription";
const EmployeeProfile = () => { const EmployeeProfile = () => {
const { profile } = useProfile(); const { profile } = useProfile();
@ -77,6 +78,14 @@ const EmployeeProfile = () => {
); );
break; break;
} }
case "setting": {
return (
<>
<ManageSubscription />
</>
);
break;
}
case "activities": { case "activities": {
return ( return (
<> <>