added xl, and doc file allow to upload and getSubscription plan api properly
This commit is contained in:
parent
9648d1a98b
commit
6e89fbd680
@ -75,6 +75,15 @@ const AddPayment = ({ onClose }) => {
|
||||
<DatePicker
|
||||
name="paymentReceivedDate"
|
||||
control={control}
|
||||
minDate={
|
||||
data?.clientSubmitedDate
|
||||
? new Date(
|
||||
new Date(data?.clientSubmitedDate).setDate(
|
||||
new Date(data?.clientSubmitedDate).getDate() + 1
|
||||
)
|
||||
)
|
||||
: null
|
||||
}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
{errors.paymentReceivedDate && (
|
||||
@ -90,7 +99,7 @@ const AddPayment = ({ onClose }) => {
|
||||
className="form-label"
|
||||
required
|
||||
>
|
||||
Payment Adjustment Head
|
||||
Payment Adjustment Head
|
||||
</Label>
|
||||
<select
|
||||
className="form-select form-select-sm "
|
||||
@ -100,12 +109,14 @@ const AddPayment = ({ onClose }) => {
|
||||
<option>Loading..</option>
|
||||
) : (
|
||||
<>
|
||||
<option value="" >Select Payment Head</option>
|
||||
{paymentTypes?.data?.sort((a, b) => a.name.localeCompare(b.name))?.map((type) => (
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
<option value="">Select Payment Head</option>
|
||||
{paymentTypes?.data
|
||||
?.sort((a, b) => a.name.localeCompare(b.name))
|
||||
?.map((type) => (
|
||||
<option key={type.id} value={type.id}>
|
||||
{type.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
@ -152,9 +163,9 @@ const AddPayment = ({ onClose }) => {
|
||||
<button
|
||||
type="reset"
|
||||
className="btn btn-label-secondary btn-sm mt-3"
|
||||
onClick={()=>{
|
||||
handleClose()
|
||||
onClose()
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
onClose();
|
||||
}}
|
||||
disabled={isPending}
|
||||
>
|
||||
@ -230,12 +241,12 @@ const AddPayment = ({ onClose }) => {
|
||||
<div className="d-flex justify-content-between">
|
||||
<span>{payment?.paymentAdjustmentHead?.name}</span>
|
||||
<span className="fs-semibold d-none d-md-block">
|
||||
{formatFigure(payment.amount, {
|
||||
type: "currency",
|
||||
currency: "INR",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
{formatFigure(payment.amount, {
|
||||
type: "currency",
|
||||
currency: "INR",
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-tiny m-0 mt-1">
|
||||
{payment?.comment}
|
||||
</p>
|
||||
|
@ -164,6 +164,31 @@ const ManageCollection = ({ collectionId, onClose }) => {
|
||||
<FormProvider {...methods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="p-0 text-start">
|
||||
<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">
|
||||
<Label required>Title</Label>
|
||||
<input
|
||||
@ -215,7 +240,7 @@ const ManageCollection = ({ collectionId, onClose }) => {
|
||||
)}
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-2">
|
||||
<Label required>Expected Date</Label>
|
||||
<Label required>Expected Payment Date</Label>
|
||||
<DatePicker
|
||||
name="exceptedPaymentDate"
|
||||
control={control}
|
||||
@ -240,34 +265,10 @@ const ManageCollection = ({ collectionId, onClose }) => {
|
||||
</small>
|
||||
)}
|
||||
</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">
|
||||
<Label htmlFor="basicAmount" className="form-label" required>
|
||||
Amount
|
||||
Basic Amount
|
||||
</Label>
|
||||
<input
|
||||
type="number"
|
||||
@ -336,12 +337,23 @@ const ManageCollection = ({ collectionId, onClose }) => {
|
||||
<span className="text-muted d-block">
|
||||
Click to select or click here to browse
|
||||
</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
|
||||
type="file"
|
||||
id="attachments"
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
accept="
|
||||
.pdf,
|
||||
.doc,
|
||||
.docx,
|
||||
.xls,
|
||||
.xlsx,
|
||||
.jpg,
|
||||
.jpeg,
|
||||
.png
|
||||
"
|
||||
multiple
|
||||
style={{ display: "none" }}
|
||||
{...register("attachments")}
|
||||
|
@ -3,11 +3,16 @@ import { z } from "zod";
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
const ALLOWED_TYPES = [
|
||||
"application/pdf",
|
||||
"application/doc",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
];
|
||||
|
||||
|
||||
export const newCollection = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title is required" }),
|
||||
projectId: z.string().trim().min(1, { message: "Project is required" }),
|
||||
|
@ -32,6 +32,16 @@ export const useModal = (modalType) => {
|
||||
|
||||
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-------------------------------------
|
||||
|
||||
@ -85,8 +95,8 @@ export const useAuthModal = () => {
|
||||
|
||||
export const useLogout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const naviget = useNavigate()
|
||||
const dispatch = useDispatch()
|
||||
const naviget = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
@ -99,12 +109,12 @@ export const useLogout = () => {
|
||||
},
|
||||
|
||||
onSuccess: (data) => {
|
||||
queryClient.clear()
|
||||
queryClient.clear();
|
||||
removeSession();
|
||||
dispatch(cacheProfileData(null))
|
||||
dispatch(cacheProfileData(null));
|
||||
|
||||
// window.location.href = "/auth/login";
|
||||
naviget("/auth/login",{replace:true})
|
||||
naviget("/auth/login", { replace: true });
|
||||
if (onSuccessCallBack) onSuccessCallBack();
|
||||
},
|
||||
|
||||
|
@ -206,8 +206,8 @@ const LandingPage = () => {
|
||||
navigation={false}
|
||||
modules={[EffectFlip, Autoplay, Pagination, Navigation]}
|
||||
className="mySwiper"
|
||||
onSlideChange={() => console.log("slide change")}
|
||||
onSwiper={(swiper) => console.log(swiper)}
|
||||
onSlideChange={() => {}}
|
||||
onSwiper={(swiper) => {}}
|
||||
>
|
||||
<SwiperSlide>
|
||||
<SwaperSlideContent
|
||||
|
@ -2,37 +2,28 @@ import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { Link } from "react-router-dom";
|
||||
import PlanCardSkeleton from "./PlanCardSkeleton";
|
||||
import { useSubscription } from "../../hooks/useAuth";
|
||||
|
||||
const SubscriptionPlans = () => {
|
||||
const [plans, setPlans] = useState([]);
|
||||
const [frequency, setFrequency] = useState(1);
|
||||
const { data, isLoading, isError, error } = useSubscription(frequency);
|
||||
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) => {
|
||||
switch (freq) {
|
||||
case 0: return "1 mo";
|
||||
case 1: return "3 mo";
|
||||
case 2: return "6 mo";
|
||||
case 3: return "1 yr";
|
||||
default: return "mo";
|
||||
case 0:
|
||||
return "1 mo";
|
||||
case 1:
|
||||
return "3 mo";
|
||||
case 2:
|
||||
return "6 mo";
|
||||
case 3:
|
||||
return "1 yr";
|
||||
default:
|
||||
return "mo";
|
||||
}
|
||||
};
|
||||
|
||||
@ -41,38 +32,49 @@ const SubscriptionPlans = () => {
|
||||
{/* Frequency Switcher */}
|
||||
<div className="text-center mb-4">
|
||||
<div className="btn-group" role="group" aria-label="Plan frequency">
|
||||
{["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map((label, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"}`}
|
||||
onClick={() => setFrequency(idx)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
{["Monthly", "Quarterly", "Half-Yearly", "Yearly"].map(
|
||||
(label, idx) => (
|
||||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
className={`btn btn-${
|
||||
frequency === idx ? "primary" : "outline-secondary"
|
||||
}`}
|
||||
onClick={() => setFrequency(idx)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cards */}
|
||||
<div className="row g-4 mt-10">
|
||||
{loading ? (
|
||||
{isLoading ? (
|
||||
// Show 3 skeletons
|
||||
<>
|
||||
<PlanCardSkeleton />
|
||||
<PlanCardSkeleton />
|
||||
<PlanCardSkeleton />
|
||||
</>
|
||||
) : plans.length === 0 ? (
|
||||
) : data.length === 0 ? (
|
||||
<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 className="card h-100 shadow-lg border-0 p-3 text-center p-10">
|
||||
{/* Header */}
|
||||
<div className="mb-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -80,7 +82,9 @@ const SubscriptionPlans = () => {
|
||||
<div className="mb-3">
|
||||
<h4 className="fw-semibold mt-auto mb-0 fs-3">
|
||||
{plan.currency?.symbol} {plan.price}
|
||||
<small className="text-muted ms-1">/ {frequencyLabel(frequency)}</small>
|
||||
<small className="text-muted ms-1">
|
||||
/ {frequencyLabel(frequency)}
|
||||
</small>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@ -133,7 +137,6 @@ const SubscriptionPlans = () => {
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -181,7 +181,7 @@ const CollectionPage = () => {
|
||||
<ConfirmModal
|
||||
type="success"
|
||||
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}
|
||||
loading={isPending}
|
||||
onSubmit={() => handleMarkedPayment(processedPayment?.invoiceId)}
|
||||
|
@ -10,6 +10,7 @@ const AuthRepository = {
|
||||
verifyOTP: (data) => api.postPublic("/api/auth/login-otp", data),
|
||||
register: (data) => api.postPublic("/api/auth/register", 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)
|
||||
logout: (data) => api.post("/api/auth/logout", data),
|
||||
@ -17,7 +18,9 @@ const AuthRepository = {
|
||||
changepassword: (data) => api.post("/api/auth/change-password", data),
|
||||
appmenu: () => api.get('/api/appmenu/get/menu'),
|
||||
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"),
|
||||
|
||||
//
|
||||
|
||||
};
|
||||
|
||||
|
@ -72,7 +72,9 @@ axiosClient.interceptors.response.use(
|
||||
if (status === 401 && !isRefreshRequest) {
|
||||
originalRequest._retry = true;
|
||||
|
||||
const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken");
|
||||
const refreshToken =
|
||||
localStorage.getItem("refreshToken") ||
|
||||
sessionStorage.getItem("refreshToken");
|
||||
|
||||
if (
|
||||
!refreshToken ||
|
||||
@ -87,7 +89,9 @@ axiosClient.interceptors.response.use(
|
||||
try {
|
||||
// Refresh token call
|
||||
const res = await axiosClient.post("/api/Auth/refresh-token", {
|
||||
token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"),
|
||||
token:
|
||||
localStorage.getItem("jwtToken") ||
|
||||
sessionStorage.getItem("jwtToken"),
|
||||
refreshToken,
|
||||
});
|
||||
|
||||
@ -144,6 +148,11 @@ export const api = {
|
||||
headers: { ...customHeaders },
|
||||
authRequired: false,
|
||||
}),
|
||||
getPublic: (url, data = {}, customHeaders = {}) =>
|
||||
apiRequest("get", url, data, {
|
||||
headers: { ...customHeaders },
|
||||
authRequired: false,
|
||||
}),
|
||||
|
||||
// Authenticated routes
|
||||
get: (url, params = {}, customHeaders = {}) =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user