- {userList.map((user) => (
-
-
-
-
-
- {user.name ? user.name[0] : "N/A"}
-
-
-
-
- {user.name || "Unknown"}
-
-
- {user.email || "No email"}
-
-
+
+ {userList.map((user) => (
+
+
+
+
+ {user.name ? user.name[0] : "U"}
+
+
+
{user.name || "Unknown"}
+
+ {user.email || "No email"}
+
-
-
-
- {user.role}{" "}
-
-
-
-
-
-
-
- ))}
-
- {hasMore && (
-
- {loading ? "Loading..." : "Load more"}
+
+
+
+ {user.role}{" "}
+
+
+
+
+
+
+
- )}
-
-
-
-
- >
+ ))}
+
+ {hasMore && (
+
+ {loading ? "Loading..." : "Load more"}
+
+ )}
+
+
+
+
);
};
diff --git a/src/components/common/registration-form.tsx b/src/components/common/registration-form.tsx
index 6ee30b4..4ce64c7 100644
--- a/src/components/common/registration-form.tsx
+++ b/src/components/common/registration-form.tsx
@@ -6,30 +6,17 @@ import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
} from "@/components/ui/form";
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from "@/components/ui/card";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { getPrice } from "@/app/actions/get-price";
import { toast } from "sonner";
@@ -44,401 +31,389 @@ import { PaymentLoading } from "../payment/payment-loading";
import { PaymentSuccessfulComponent } from "../payment/payment-successful";
declare global {
- interface Window {
- Razorpay: any;
- }
+ interface Window {
+ Razorpay: any;
+ }
}
interface ResponseInterface {
- orderId?: string;
- status: number;
- error?: string;
+ orderId?: string;
+ status: number;
+ error?: string;
}
interface RazorpayResponse {
- razorpay_order_id: string;
- razorpay_payment_id: string;
- razorpay_signature: string;
+ razorpay_order_id: string;
+ razorpay_payment_id: string;
+ razorpay_signature: string;
}
-export const baseSchema = z.object({
- designation: z.enum(["student", "faculty", "employee"]),
- name: z.string().min(2, { message: "Name must be at least 2 characters." }),
- email: z.string().email({ message: "Invalid email address." }),
- phone: z
- .string()
- .regex(/^\d{10}$/, { message: "Phone number must be 10 digits." }),
- photo: z.string(),
- couponCode: z.string().optional(),
+const baseSchema = z.object({
+ name: z.string().min(1, "Name is required"),
+ email: z.string().email("Invalid email"),
+ phone: z.string().min(10, "Phone number must be at least 10 digits"),
+ photo: z.string().min(1, "Photo is required"),
+ designation: z.enum(["student", "faculty", "employee"]),
});
+const studentSchema = baseSchema.extend({
+ usn: z.string().min(1, "USN is required"),
+ idCard: z.string().min(1, "ID Card is required"),
+});
+
+const facultyOrEmployeeSchema = baseSchema;
+
export interface FormDataInterface {
- designation: "student" | "faculty" | "employee";
- name: string;
- email: string;
- phone: string;
- photo: string;
- idCard?: string;
- usn?: string;
- amount: string;
- couponCode?: string;
+ designation: "student" | "faculty" | "employee";
+ name: string;
+ email: string;
+ phone: string;
+ photo: string;
+ idCard?: string;
+ usn?: string;
+ amount: string;
+ couponCode?: string;
}
-export const studentSchema = baseSchema.extend({
- usn: z.string().min(1, { message: "USN is required for students." }),
- idCard: z.string(),
-});
-
export default function RegistrationForm() {
- const [step, setStep] = useState(1);
- const [pricing, setPricing] = useState({
- basePrice: basePrice,
- discountAmount: initialdiscount,
- finalPrice: basePrice,
- });
- const [isProcessing, setIsProcessing] = useState(false);
- const [loading, setLoading] = useState(false);
- const [success, setSuccess] = useState(false);
+ const [step, setStep] = useState(1);
+ const [pricing, setPricing] = useState({
+ basePrice: basePrice,
+ discountAmount: initialdiscount,
+ finalPrice: basePrice,
+ });
+ const [isProcessing, setIsProcessing] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [success, setSuccess] = useState(false);
- const { data: session } = useSession();
+ const { data: session } = useSession();
- const form = useForm<
- z.infer
- >({
- resolver: zodResolver(baseSchema),
- defaultValues: {
- designation: "student",
- name: "",
- email: "",
- phone: "",
- couponCode: "",
- },
- });
+ const form = useForm<
+ z.infer & {
+ couponCode?: string;
+ }
+ >({
+ resolver: zodResolver(facultyOrEmployeeSchema),
+ defaultValues: {
+ name: "",
+ email: "",
+ phone: "",
+ photo: "",
+ idCard: "",
+ usn: "",
+ designation: "student",
+ couponCode: "",
+ },
+ });
- // const handleSubmitForm = async () => {
- // const data = form.getValues();
- // const resp = await submitForm(data as FormDataInterface);
- // };
+ // const handleSubmitForm = async () => {
+ // const data = form.getValues();
+ // const resp = await submitForm(data as FormDataInterface);
+ // };
- const handlePayment = async () => {
- setIsProcessing(true);
- const couponCode = form.getValues("couponCode");
- try {
- const response = await fetch("/api/create-order", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ amount: pricing.finalPrice }),
- });
- const data: Promise = response.json();
+ const handlePayment = async () => {
+ setIsProcessing(true);
+ const couponCode = form.getValues("couponCode");
+ try {
+ const response = await fetch("/api/create-order", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ amount: pricing.finalPrice }),
+ });
+ const data: Promise = response.json();
- const options = {
- key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID,
- amount: pricing.finalPrice * 100,
- currency: "INR",
- name: "Test Name",
- description: "Test Transaction",
- order_id: (await data).orderId,
- handler: async (response: RazorpayResponse) => {
- const resp = await fetch("/api/verify-order", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- orderId: response.razorpay_order_id,
- razorpayPaymentId: response.razorpay_payment_id,
- razorpaySignature: response.razorpay_signature,
- amount: pricing.finalPrice,
- }),
- });
+ const options = {
+ key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID,
+ amount: pricing.finalPrice * 100,
+ currency: "INR",
+ name: "St Joseph Engineering College",
+ description: "Test Transaction",
+ order_id: (await data).orderId,
+ handler: async (response: RazorpayResponse) => {
+ const resp = await fetch("/api/verify-order", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ orderId: response.razorpay_order_id,
+ razorpayPaymentId: response.razorpay_payment_id,
+ razorpaySignature: response.razorpay_signature,
+ amount: pricing.finalPrice,
+ }),
+ });
- const data = await resp.json();
- console.log(data);
- if (data.isOk) {
- await invalidateCouponCode(couponCode ?? "", session!);
- const formResponse = form.getValues();
- await submitForm(
- formResponse as FormDataInterface,
- pricing.finalPrice
- );
- setIsProcessing(false);
- setSuccess(true);
- } else {
- setIsProcessing(false);
- alert("Payment failed");
- }
- },
- // change to dynamic
- notes: {
- customerName: form.getValues("name"),
- customerEmail: session?.user.email,
- customerContact: form.getValues("phone"),
- },
- prefill: {
- name: form.getValues("name"),
- email: session?.user.email,
- constact: form.getValues("phone"),
- },
- theme: {
- color: "#3399cc",
- },
- };
- const rzp1 = new window.Razorpay(options);
- rzp1.open();
- } catch (error) {
- toast.error(`Some error ${error}`);
- }
- };
+ const data = await resp.json();
+ console.log(data);
+ if (data.isOk) {
+ await invalidateCouponCode(couponCode ?? "", session!);
+ const formResponse = form.getValues();
+ await submitForm(formResponse as FormDataInterface, pricing.finalPrice);
+ setIsProcessing(false);
+ setSuccess(true);
+ } else {
+ setIsProcessing(false);
+ alert("Payment failed");
+ }
+ },
+ // change to dynamic
+ notes: {
+ customerName: form.getValues("name"),
+ customerEmail: session?.user.email,
+ customerContact: form.getValues("phone"),
+ },
+ prefill: {
+ name: form.getValues("name"),
+ email: session?.user.email,
+ constact: form.getValues("phone"),
+ },
+ theme: {
+ color: "#3399cc",
+ },
+ };
+ const rzp1 = new window.Razorpay(options);
+ rzp1.open();
+ } catch (error) {
+ toast.error(`Some error ${error}`);
+ }
+ };
- const onSubmit = async (
- values: z.infer<
- typeof studentSchema | typeof baseSchema | typeof baseSchema
- >
- ) => {
- await handlePayment();
- // Handle form submission here
- };
+ const onSubmit = async (values: z.infer) => {
+ await handlePayment();
+ // Handle form submission here
+ };
- const verifyCoupon = async () => {
- const couponCode = form.getValues("couponCode");
- try {
- const { basePrice, discountAmount, finalPrice } = await getPrice(
- couponCode
- );
- setPricing({ basePrice, discountAmount, finalPrice });
- toast.success("Coupon applied successfully");
- } catch (e) {
- console.error(e);
- const message = getErrorMessage(e);
- toast.error(`${message}`);
- }
- };
+ const verifyCoupon = async () => {
+ const couponCode = form.getValues("couponCode");
+ try {
+ const { basePrice, discountAmount, finalPrice } = await getPrice(couponCode);
+ setPricing({ basePrice, discountAmount, finalPrice });
+ toast.success("Coupon applied successfully");
+ } catch (e) {
+ console.error(e);
+ const message = getErrorMessage(e);
+ toast.error(`${message}`);
+ }
+ };
- const handleNext = async () => {
- let isValid = false;
- if (step === 1) {
- isValid = await form.trigger(["designation"]);
- } else if (step === 2) {
- const designation = form.getValues("designation");
- if (designation === "student") {
- isValid = await form.trigger([
- "name",
- "email",
- "phone",
- "usn",
- "idCard",
- "photo",
- ]);
- } else if (designation === "faculty" || designation === "employee") {
- isValid = await form.trigger(["name", "email", "phone", "photo"]);
- }
- }
+ const handleNext = async () => {
+ let isValid = false;
+
+ if (step === 1) {
+ isValid = await form.trigger(["designation"]);
+ } else if (step === 2) {
+ const designation = form.getValues("designation");
+
+ // Trigger different validations based on the designation
+ if (designation === "student") {
+ isValid = await form.trigger(["name", "email", "phone", "usn", "idCard", "photo"]);
+ } else if (designation === "faculty" || designation === "employee") {
+ isValid = await form.trigger(["name", "email", "phone", "photo"]);
+ }
+ }
- if (isValid) {
- setStep(step + 1);
+ if (isValid) {
+ setStep(step + 1);
+ }
+ };
+ if (isProcessing) {
+ return (
+
+ );
+ }
+ if (success) {
+ return (
+
+ );
}
- };
- if (isProcessing) {
- return (
-
- );
- }
- if (success) {
- return (
-
- );
- }
- return (
-
-
-
- Registration Form
- Step {step} of 3
-
-
-
-
-
-
- {step > 1 && (
- setStep(step - 1)}>
- Previous
-
- )}
- {step < 3 ? (
- Next
- ) : (
-
- {isProcessing ? "Processing..." : "Pay Now"}
-
- )}
-
-
- );
+
+
+ );
}
diff --git a/src/components/common/searchable-infinite-scroll-table.tsx b/src/components/common/searchable-infinite-scroll-table.tsx
index 964c344..3dc354a 100644
--- a/src/components/common/searchable-infinite-scroll-table.tsx
+++ b/src/components/common/searchable-infinite-scroll-table.tsx
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState, useCallback } from "react";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Loader2, Search } from "lucide-react";
+import { Badge } from "@/components/ui/badge";
import axios from "axios";
import debounce from "lodash.debounce";
@@ -18,7 +19,7 @@ interface TableData {
amount: number;
}
-export function SearchableInfiniteScrollTable() {
+export function SearchableInfiniteScrollTable({totalPayments}: {totalPayments: number}) {
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
@@ -26,7 +27,6 @@ export function SearchableInfiniteScrollTable() {
const [searchTerm, setSearchTerm] = useState("");
const [hasMoreData, setHasMoreData] = useState(true);
const loaderRef = useRef(null);
- const observerRef = useRef(null);
const getPaymentDetails = async (page: number, query: string) => {
if (isLoading || !hasMoreData) return;
@@ -37,19 +37,18 @@ export function SearchableInfiniteScrollTable() {
`/api/users/payment?page=${page}&search=${encodeURIComponent(query)}`
);
const users = response.data.users;
-
if (users.length === 0) {
- setHasMoreData(false); // No more data to load
+ setHasMoreData(false);
}
setData((prevData) => {
const newData = [...prevData, ...users];
- // Remove duplicates
const uniqueData = Array.from(
new Map(newData.map((item) => [item.razorpayPaymentId, item])).values()
);
return uniqueData;
});
+
setPage((prevPage) => prevPage + 1);
} catch (error) {
console.error("Error fetching payment details:", error);
@@ -58,20 +57,22 @@ export function SearchableInfiniteScrollTable() {
}
};
- const loadMoreData = () => {
+ const loadMoreData = useCallback(() => {
if (searchTerm === "") {
getPaymentDetails(page, "");
}
- };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [page, searchTerm]);
const fetchSearchResults = useCallback(async (query: string) => {
- setPage(1); // Reset page number
- setHasMoreData(true); // Reset hasMoreData
+ setPage(1);
+ setHasMoreData(true);
try {
const response = await axios.get(`/api/users/payment?page=1&search=${encodeURIComponent(query)}`);
const users = response.data.users;
- setData(users); // Set new data from search
- setFilteredData(users); // Set filtered data to the same as new data
+ const total = response.data.total;
+ setData(users);
+ setFilteredData(users);
} catch (error) {
console.error("Error fetching payment details:", error);
}
@@ -81,20 +82,19 @@ export function SearchableInfiniteScrollTable() {
const debouncedFetch = useCallback(
debounce((query: string) => {
fetchSearchResults(query);
- }, 500),
- []
+ }, 300),
+ [fetchSearchResults]
);
const handleSearch = (event: React.ChangeEvent) => {
const value = event.target.value;
setSearchTerm(value);
- debouncedFetch(value); // Use debounced fetch function
+ debouncedFetch(value);
};
useEffect(() => {
- loadMoreData(); // Initial load
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ loadMoreData();
+ }, [loadMoreData]);
useEffect(() => {
const observer = new IntersectionObserver(
@@ -115,50 +115,56 @@ export function SearchableInfiniteScrollTable() {
// eslint-disable-next-line react-hooks/exhaustive-deps
observer.unobserve(loaderRef.current);
}
- observer.disconnect();
};
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading]);
+ }, [isLoading, loadMoreData]);
return (
-
-
-
+
+
+ Total Payments: {totalPayments}
+
+
+
+
+
-
-
-
- Name
- Email
- USN
- Payment ID
- Contact Number
- Amount
-
-
-
- {(searchTerm ? filteredData : data).map((item, index) => (
-
- {item.user.name}
- {item.user.email}
- {item.usn}
- {item.razorpayPaymentId}
- {item.contactNumber}
- ₹{item.amount.toFixed(2)}
+
+
+
+
+ Name
+ Email
+ USN
+ Payment ID
+ Contact Number
+ Amount
- ))}
-
-
+
+
+ {(searchTerm ? filteredData : data).map((item, index) => (
+
+ {item.user.name}
+ {item.user.email}
+ {item.usn}
+ {item.razorpayPaymentId}
+ {item.contactNumber}
+ ₹{item.amount.toFixed(2)}
+
+ ))}
+
+
+
{searchTerm === "" && hasMoreData && (
{isLoading && }
@@ -166,4 +172,4 @@ export function SearchableInfiniteScrollTable() {
)}
);
-}
+}
\ No newline at end of file
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000..e87d62b
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+export interface BadgeProps
+ extends React.HTMLAttributes
,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }