added xl, and doc file allow to upload and getSubscription plan api properly

This commit is contained in:
pramod.mahajan 2025-10-15 17:31:58 +05:30
parent 9648d1a98b
commit 6e89fbd680
9 changed files with 147 additions and 94 deletions

View File

@ -75,6 +75,15 @@ const AddPayment = ({ onClose }) => {
<DatePicker <DatePicker
name="paymentReceivedDate" name="paymentReceivedDate"
control={control} control={control}
minDate={
data?.clientSubmitedDate
? new Date(
new Date(data?.clientSubmitedDate).setDate(
new Date(data?.clientSubmitedDate).getDate() + 1
)
)
: null
}
maxDate={new Date()} maxDate={new Date()}
/> />
{errors.paymentReceivedDate && ( {errors.paymentReceivedDate && (
@ -90,7 +99,7 @@ const AddPayment = ({ onClose }) => {
className="form-label" className="form-label"
required required
> >
Payment Adjustment Head Payment Adjustment Head
</Label> </Label>
<select <select
className="form-select form-select-sm " className="form-select form-select-sm "
@ -100,12 +109,14 @@ const AddPayment = ({ onClose }) => {
<option>Loading..</option> <option>Loading..</option>
) : ( ) : (
<> <>
<option value="" >Select Payment Head</option> <option value="">Select Payment Head</option>
{paymentTypes?.data?.sort((a, b) => a.name.localeCompare(b.name))?.map((type) => ( {paymentTypes?.data
<option key={type.id} value={type.id}> ?.sort((a, b) => a.name.localeCompare(b.name))
{type.name} ?.map((type) => (
</option> <option key={type.id} value={type.id}>
))} {type.name}
</option>
))}
</> </>
)} )}
</select> </select>
@ -152,9 +163,9 @@ const AddPayment = ({ onClose }) => {
<button <button
type="reset" type="reset"
className="btn btn-label-secondary btn-sm mt-3" className="btn btn-label-secondary btn-sm mt-3"
onClick={()=>{ onClick={() => {
handleClose() handleClose();
onClose() onClose();
}} }}
disabled={isPending} disabled={isPending}
> >
@ -230,12 +241,12 @@ const AddPayment = ({ onClose }) => {
<div className="d-flex justify-content-between"> <div className="d-flex justify-content-between">
<span>{payment?.paymentAdjustmentHead?.name}</span> <span>{payment?.paymentAdjustmentHead?.name}</span>
<span className="fs-semibold d-none d-md-block"> <span className="fs-semibold d-none d-md-block">
{formatFigure(payment.amount, { {formatFigure(payment.amount, {
type: "currency", type: "currency",
currency: "INR", currency: "INR",
})} })}
</span> </span>
</div> </div>
<p className="text-tiny m-0 mt-1"> <p className="text-tiny m-0 mt-1">
{payment?.comment} {payment?.comment}
</p> </p>

View File

@ -164,6 +164,31 @@ const ManageCollection = ({ collectionId, onClose }) => {
<FormProvider {...methods}> <FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="p-0 text-start"> <form onSubmit={handleSubmit(onSubmit)} className="p-0 text-start">
<div className="row px-md-1 px-0"> <div className="row px-md-1 px-0">
<div className="col-12 col-md-6 mb-2">
<Label className="form-label" required>
Select Project
</Label>
<select
className="form-select form-select-sm"
{...register("projectId")}
>
<option value="">Select Project</option>
{projectLoading ? (
<option>Loading...</option>
) : (
projectNames?.map((project) => (
<option key={project.id} value={project.id}>
{project.name}
</option>
))
)}
</select>
{errors.projectId && (
<small className="danger-text">
{errors.projectId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 mb-2"> <div className="col-12 col-md-6 mb-2">
<Label required>Title</Label> <Label required>Title</Label>
<input <input
@ -215,7 +240,7 @@ const ManageCollection = ({ collectionId, onClose }) => {
)} )}
</div> </div>
<div className="col-12 col-md-6 mb-2"> <div className="col-12 col-md-6 mb-2">
<Label required>Expected Date</Label> <Label required>Expected Payment Date</Label>
<DatePicker <DatePicker
name="exceptedPaymentDate" name="exceptedPaymentDate"
control={control} control={control}
@ -240,34 +265,10 @@ const ManageCollection = ({ collectionId, onClose }) => {
</small> </small>
)} )}
</div> </div>
<div className="col-12 col-md-6 mb-2">
<Label className="form-label" required>
Select Project
</Label>
<select
className="form-select form-select-sm"
{...register("projectId")}
>
<option value="">Select Project</option>
{projectLoading ? (
<option>Loading...</option>
) : (
projectNames?.map((project) => (
<option key={project.id} value={project.id}>
{project.name}
</option>
))
)}
</select>
{errors.projectId && (
<small className="danger-text">
{errors.projectId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 mb-2"> <div className="col-12 col-md-6 mb-2">
<Label htmlFor="basicAmount" className="form-label" required> <Label htmlFor="basicAmount" className="form-label" required>
Amount Basic Amount
</Label> </Label>
<input <input
type="number" type="number"
@ -336,12 +337,23 @@ const ManageCollection = ({ collectionId, onClose }) => {
<span className="text-muted d-block"> <span className="text-muted d-block">
Click to select or click here to browse Click to select or click here to browse
</span> </span>
<small className="text-muted">(PDF, JPG, PNG, max 5MB)</small> <small className="text-muted">
(PDF, JPG, PNG,Doc,docx,xls,xlsx max 5MB)
</small>
<input <input
type="file" type="file"
id="attachments" id="attachments"
accept=".pdf,.jpg,.jpeg,.png" accept="
.pdf,
.doc,
.docx,
.xls,
.xlsx,
.jpg,
.jpeg,
.png
"
multiple multiple
style={{ display: "none" }} style={{ display: "none" }}
{...register("attachments")} {...register("attachments")}

View File

@ -3,11 +3,16 @@ import { z } from "zod";
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = [ const ALLOWED_TYPES = [
"application/pdf", "application/pdf",
"application/doc",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"image/png", "image/png",
"image/jpg", "image/jpg",
"image/jpeg", "image/jpeg",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
]; ];
export const newCollection = z.object({ export const newCollection = z.object({
title: z.string().trim().min(1, { message: "Title is required" }), title: z.string().trim().min(1, { message: "Title is required" }),
projectId: z.string().trim().min(1, { message: "Project is required" }), projectId: z.string().trim().min(1, { message: "Project is required" }),

View File

@ -32,6 +32,16 @@ export const useModal = (modalType) => {
return { isOpen, onOpen, onClose, onToggle }; return { isOpen, onOpen, onClose, onToggle };
}; };
export const useSubscription = (frequency) => {
return useQuery({
queryKey: ["subscriptionPlans", frequency],
queryFn: async () => {
debugger
const resp = await AuthRepository.getSubscription(frequency);
return resp.data;
},
});
};
// -------------------APIHook------------------------------------- // -------------------APIHook-------------------------------------
@ -85,8 +95,8 @@ export const useAuthModal = () => {
export const useLogout = () => { export const useLogout = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const naviget = useNavigate() const naviget = useNavigate();
const dispatch = useDispatch() const dispatch = useDispatch();
return useMutation({ return useMutation({
mutationFn: async () => { mutationFn: async () => {
@ -99,12 +109,12 @@ export const useLogout = () => {
}, },
onSuccess: (data) => { onSuccess: (data) => {
queryClient.clear() queryClient.clear();
removeSession(); removeSession();
dispatch(cacheProfileData(null)) dispatch(cacheProfileData(null));
// window.location.href = "/auth/login"; // window.location.href = "/auth/login";
naviget("/auth/login",{replace:true}) naviget("/auth/login", { replace: true });
if (onSuccessCallBack) onSuccessCallBack(); if (onSuccessCallBack) onSuccessCallBack();
}, },

View File

@ -206,8 +206,8 @@ const LandingPage = () => {
navigation={false} navigation={false}
modules={[EffectFlip, Autoplay, Pagination, Navigation]} modules={[EffectFlip, Autoplay, Pagination, Navigation]}
className="mySwiper" className="mySwiper"
onSlideChange={() => console.log("slide change")} onSlideChange={() => {}}
onSwiper={(swiper) => console.log(swiper)} onSwiper={(swiper) => {}}
> >
<SwiperSlide> <SwiperSlide>
<SwaperSlideContent <SwaperSlideContent

View File

@ -2,37 +2,28 @@ import React, { useState, useEffect } from "react";
import axios from "axios"; import axios from "axios";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import PlanCardSkeleton from "./PlanCardSkeleton"; import PlanCardSkeleton from "./PlanCardSkeleton";
import { useSubscription } from "../../hooks/useAuth";
const SubscriptionPlans = () => { const SubscriptionPlans = () => {
const [plans, setPlans] = useState([]); const [plans, setPlans] = useState([]);
const [frequency, setFrequency] = useState(1); const [frequency, setFrequency] = useState(1);
const { data, isLoading, isError, error } = useSubscription(frequency);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchPlans = async () => {
try {
setLoading(true);
const res = await axios.get(
`http://localhost:5032/api/market/list/subscription-plan?frequency=${frequency}`,
{ headers: { "Content-Type": "application/json" } }
);
setPlans(res.data?.data || []);
} catch (err) {
console.error("Error fetching plans:", err);
} finally {
setLoading(false);
}
};
fetchPlans();
}, [frequency]);
const frequencyLabel = (freq) => { const frequencyLabel = (freq) => {
switch (freq) { switch (freq) {
case 0: return "1 mo"; case 0:
case 1: return "3 mo"; return "1 mo";
case 2: return "6 mo"; case 1:
case 3: return "1 yr"; return "3 mo";
default: return "mo"; case 2:
return "6 mo";
case 3:
return "1 yr";
default:
return "mo";
} }
}; };
@ -41,38 +32,49 @@ const SubscriptionPlans = () => {
{/* Frequency Switcher */} {/* Frequency Switcher */}
<div className="text-center mb-4"> <div className="text-center mb-4">
<div className="btn-group" role="group" aria-label="Plan frequency"> <div className="btn-group" role="group" aria-label="Plan frequency">
{["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map((label, idx) => ( {["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map(
<button (label, idx) => (
key={idx} <button
type="button" key={idx}
className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"}`} type="button"
onClick={() => setFrequency(idx)} className={`btn btn-${
> frequency === idx ? "primary" : "outline-secondary"
{label} }`}
</button> onClick={() => setFrequency(idx)}
))} >
{label}
</button>
)
)}
</div> </div>
</div> </div>
{/* Cards */} {/* Cards */}
<div className="row g-4 mt-10"> <div className="row g-4 mt-10">
{loading ? ( {isLoading ? (
// Show 3 skeletons // Show 3 skeletons
<> <>
<PlanCardSkeleton /> <PlanCardSkeleton />
<PlanCardSkeleton /> <PlanCardSkeleton />
<PlanCardSkeleton /> <PlanCardSkeleton />
</> </>
) : plans.length === 0 ? ( ) : data.length === 0 ? (
<div className="text-center">No plans found</div> <div className="text-center">No plans found</div>
) : isError ? (
<div className="text-start bg-light">
<p>{error.message}</p>
<p>{error.name}</p>
</div>
) : ( ) : (
plans.map((plan) => ( data.map((plan) => (
<div key={plan.id} className="col-xl-4 col-lg-6 col-md-6"> <div key={plan.id} className="col-xl-4 col-lg-6 col-md-6">
<div className="card h-100 shadow-lg border-0 p-3 text-center p-10"> <div className="card h-100 shadow-lg border-0 p-3 text-center p-10">
{/* Header */} {/* Header */}
<div className="mb-3"> <div className="mb-3">
<i className="bx bxs-package text-primary fs-1 mb-2"></i> <i className="bx bxs-package text-primary fs-1 mb-2"></i>
<p className="card-title fs-3 fw-bold mb-1">{plan.planName}</p> <p className="card-title fs-3 fw-bold mb-1">
{plan.planName}
</p>
<p className="text-muted mb-0 fs-5">{plan.description}</p> <p className="text-muted mb-0 fs-5">{plan.description}</p>
</div> </div>
@ -80,7 +82,9 @@ const SubscriptionPlans = () => {
<div className="mb-3"> <div className="mb-3">
<h4 className="fw-semibold mt-auto mb-0 fs-3"> <h4 className="fw-semibold mt-auto mb-0 fs-3">
{plan.currency?.symbol} {plan.price} {plan.currency?.symbol} {plan.price}
<small className="text-muted ms-1">/ {frequencyLabel(frequency)}</small> <small className="text-muted ms-1">
/ {frequencyLabel(frequency)}
</small>
</h4> </h4>
</div> </div>
@ -133,7 +137,6 @@ const SubscriptionPlans = () => {
)) ))
)} )}
</div> </div>
</div> </div>
); );
}; };

View File

@ -181,7 +181,7 @@ const CollectionPage = () => {
<ConfirmModal <ConfirmModal
type="success" type="success"
header="Payment Successful Received" header="Payment Successful Received"
message="Your payment has been processed successfully. Do you want to continue?" message="Payment has been recored successfully."
isOpen={processedPayment?.isOpen} isOpen={processedPayment?.isOpen}
loading={isPending} loading={isPending}
onSubmit={() => handleMarkedPayment(processedPayment?.invoiceId)} onSubmit={() => handleMarkedPayment(processedPayment?.invoiceId)}

View File

@ -10,6 +10,7 @@ const AuthRepository = {
verifyOTP: (data) => api.postPublic("/api/auth/login-otp", data), verifyOTP: (data) => api.postPublic("/api/auth/login-otp", data),
register: (data) => api.postPublic("/api/auth/register", data), register: (data) => api.postPublic("/api/auth/register", data),
sendMail: (data) => api.postPublic("/api/auth/sendmail", data), sendMail: (data) => api.postPublic("/api/auth/sendmail", data),
getSubscription:(frequency)=> api.getPublic(`/api/market/list/subscription-plan?frequency=${frequency}`),
// Protected routes (require auth token) // Protected routes (require auth token)
logout: (data) => api.post("/api/auth/logout", data), logout: (data) => api.post("/api/auth/logout", data),
@ -17,7 +18,9 @@ const AuthRepository = {
changepassword: (data) => api.post("/api/auth/change-password", data), changepassword: (data) => api.post("/api/auth/change-password", data),
appmenu: () => api.get('/api/appmenu/get/menu'), appmenu: () => api.get('/api/appmenu/get/menu'),
selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`), selectTenant: (tenantId) => api.post(`/api/Auth/select-tenant/${tenantId}`),
getTenantList: () => api.get("/api/Auth/get/user/tenants"), getTenantList: () => api.get("/api/Auth/get/user/tenants"),
//
}; };

View File

@ -72,7 +72,9 @@ axiosClient.interceptors.response.use(
if (status === 401 && !isRefreshRequest) { if (status === 401 && !isRefreshRequest) {
originalRequest._retry = true; originalRequest._retry = true;
const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken"); const refreshToken =
localStorage.getItem("refreshToken") ||
sessionStorage.getItem("refreshToken");
if ( if (
!refreshToken || !refreshToken ||
@ -87,7 +89,9 @@ axiosClient.interceptors.response.use(
try { try {
// Refresh token call // Refresh token call
const res = await axiosClient.post("/api/Auth/refresh-token", { const res = await axiosClient.post("/api/Auth/refresh-token", {
token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"), token:
localStorage.getItem("jwtToken") ||
sessionStorage.getItem("jwtToken"),
refreshToken, refreshToken,
}); });
@ -144,6 +148,11 @@ export const api = {
headers: { ...customHeaders }, headers: { ...customHeaders },
authRequired: false, authRequired: false,
}), }),
getPublic: (url, data = {}, customHeaders = {}) =>
apiRequest("get", url, data, {
headers: { ...customHeaders },
authRequired: false,
}),
// Authenticated routes // Authenticated routes
get: (url, params = {}, customHeaders = {}) => get: (url, params = {}, customHeaders = {}) =>