diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 01e4325..62fc9de 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,8 +37,7 @@ jobs: echo "TURSO_AUTH_TOKEN is set: ${{ env.TURSO_AUTH_TOKEN != '' }}" echo "TURSO_DATABASE_URL is set: ${{ env.TURSO_DATABASE_URL != '' }}" echo "RESEND_API_KEY" is set ${{ env.RESEND_API_KEY != '' }} - echo "EDGE_STORE_ACCESS_KEY" is set ${{ env.EDGE_STORE_ACCESS_KEY != '' }} - echo "EDGE_STORE_SECRET_KEY" is set ${{ env.EDGE_STORE_SECRET_KEY != '' }} + echo env: NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }} @@ -48,8 +47,7 @@ jobs: TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }} TURSO_DATABASE_URL: ${{ secrets.TURSO_DATABASE_URL }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} - EDGE_STORE_ACCESS_KEY: ${{ secrets.EDGE_STORE_ACCESS_KEY }} - EDGE_STORE_SECRET_KEY: ${{ secrets.EDGE_STORE_SECRET_KEY }} + - name: Build project run: npm run build @@ -64,8 +62,7 @@ jobs: RAZORPAY_KEY_ID: ${{ secrets.RAZORPAY_KEY_ID }} RAZORPAY_SECRET: ${{ secrets.RAZORPAY_SECRET }} NEXT_PUBLIC_RAZORPAY_KEY_ID: ${{ secrets.NEXT_PUBLIC_RAZORPAY_KEY_ID }} - EDGE_STORE_ACCESS_KEY: ${{ secrets.EDGE_STORE_ACCESS_KEY }} - EDGE_STORE_SECRET_KEY: ${{ secrets.EDGE_STORE_SECRET_KEY }} + - name: Upload build artifacts (optional) if: always() diff --git a/src/app/api/edgestore/[...edgestore]/route.ts b/src/app/api/edgestore/[...edgestore]/route.ts deleted file mode 100644 index 6ebc6e6..0000000 --- a/src/app/api/edgestore/[...edgestore]/route.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { initEdgeStore } from "@edgestore/server"; -import { createEdgeStoreNextHandler } from "@edgestore/server/adapters/next/app"; -const es = initEdgeStore.create(); -/** - * This is the main router for the Edge Store buckets. - */ -const edgeStoreRouter = es.router({ - publicFiles: es.fileBucket(), -}); -const handler = createEdgeStoreNextHandler({ - router: edgeStoreRouter, -}); -export { handler as GET, handler as POST }; -/** - * This type is used to create the type-safe client for the frontend. - */ -export type EdgeStoreRouter = typeof edgeStoreRouter; diff --git a/src/components/common/registration-form.tsx b/src/components/common/registration-form.tsx index 6884f50..df0328b 100644 --- a/src/components/common/registration-form.tsx +++ b/src/components/common/registration-form.tsx @@ -4,25 +4,46 @@ import { getPrice } from "@/app/actions/get-price"; import { invalidateCouponCode } from "@/app/actions/invalidate-coupon"; import { submitForm } from "@/app/actions/submit-form"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { basePrice, initialdiscount, sjecFacultyPrice, sjecStudentPrice } from "@/constants"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + basePrice, + initialdiscount, + sjecFacultyPrice, + sjecStudentPrice, +} from "@/constants"; import { getSjecMemberType } from "@/lib/helper"; import { FormDataInterface } from "@/types"; import getErrorMessage from "@/utils/getErrorMessage"; -import { useUploadThing } from "@/utils/uploadthing"; -import { baseSchema, studentFormSchema, studentSchema } from "@/utils/zod-schemas"; +import { + baseSchema, + studentFormSchema, + studentSchema, +} from "@/utils/zod-schemas"; import { zodResolver } from "@hookform/resolvers/zod"; import { signIn, signOut, useSession } from "next-auth/react"; import Script from "next/script"; @@ -32,621 +53,780 @@ import { toast } from "sonner"; import * as z from "zod"; import { PaymentLoading } from "../payment/payment-loading"; import { PaymentSuccessfulComponent } from "../payment/payment-successful"; -import { FileUpload } from "../ui/file-upload"; import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; import InfoButton from "../ui/info-button"; import { redirect } from "next/navigation"; -import { useEdgeStore } from "../../lib/edgestore"; -import { progress } from "framer-motion"; +import { UploadDropzone } from "@uploadthing/react"; +import { OurFileRouter } from "@/app/api/uploadthing/core"; declare global { - interface Window { - Razorpay: any; - } + interface Window { + Razorpay: any; + } } type FormSchema = z.infer; type UploadedFile = { - id: string; - files: File[]; + id: string; + files: File[]; }; export default function RegistrationForm() { - const [step, setStep] = useState(1); - const [uploadedFiles, setUploadedFiles] = useState([]); - const [isProcessing, setIsProcessing] = useState(false); - const [success, setSuccess] = useState(false); - const [memberType, setMemberType] = useState<"student" | "faculty" | "external">("external"); - const [pricing, setPricing] = useState({ - basePrice: basePrice, - discountAmount: initialdiscount, - finalPrice: basePrice, - }); - - const { data: session } = useSession(); - const { edgestore } = useEdgeStore(); - - if (!session) { - redirect("/auth/signin/?callbackUrl=/register"); - } - - useEffect(() => { - setMemberType(getSjecMemberType(session?.user.email!)); - setPricing((prevPricing) => ({ - ...prevPricing, - finalPrice: - memberType === "student" - ? sjecStudentPrice - : memberType === "faculty" - ? sjecFacultyPrice - : basePrice, - })); - }, [session?.user.email, memberType]); - - const form = useForm({ - resolver: zodResolver(baseSchema), - defaultValues: { - designation: getSjecMemberType(session?.user.email!), - name: session?.user.name!, - email: session?.user.email!, - phone: "", - entityName: "", - couponCode: "", - foodPreference: "veg", - }, - }); - - const handleFileUploadToEdgeStore = async (files: File[]) => { - let resp; - try { - if (files.length === 1) { - resp = await edgestore.publicFiles.upload({ - file: files[0], - onProgressChange: (progress) => { - console.log(progress); - }, - }); - form.setValue("photo", resp.url); - } else if (files.length === 2) { - resp = await Promise.all( - files.map((file) => - edgestore.publicFiles.upload({ - file: file, - onProgressChange: (progress) => { - console.log(progress); - }, - }) - ) - ); - form.setValue("photo", resp[0].url); - form.setValue("idCard", resp[1].url); - } - return true; - } catch (error) { - console.error("File upload failed:", error); - return false; - } - }; - - const handleFileUpload = (id: "idCard" | "photo", files: File[]) => { - setUploadedFiles((prevFiles) => { - const existing = prevFiles.find((file) => file.id === id); - if (existing) { - return prevFiles.map((file) => (file.id === id ? { ...file, files } : file)); - } else { - return [...prevFiles, { id, files }]; - } - }); - - form.setValue(id, files.map((file) => file.name).join(", ")); - }; - - const handlePayment = async () => { - setIsProcessing(true); - const couponCode = form.getValues("couponCode"); - - try { - // Create the order on the backend - const response = await fetch("/api/create-order", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ amount: pricing.finalPrice }), + const [step, setStep] = useState(1); + const [isProcessing, setIsProcessing] = useState(false); + const [success, setSuccess] = useState(false); + const [memberType, setMemberType] = useState< + "student" | "faculty" | "external" + >("external"); + const [pricing, setPricing] = useState({ + basePrice: basePrice, + discountAmount: initialdiscount, + finalPrice: basePrice, + }); + + const { data: session } = useSession(); + + if (!session) { + redirect("/auth/signin/?callbackUrl=/register"); + } + + useEffect(() => { + setMemberType(getSjecMemberType(session?.user.email!)); + setPricing((prevPricing) => ({ + ...prevPricing, + finalPrice: + memberType === "student" + ? sjecStudentPrice + : memberType === "faculty" + ? sjecFacultyPrice + : basePrice, + })); + }, [session?.user.email, memberType]); + + const form = useForm({ + resolver: zodResolver(baseSchema), + defaultValues: { + designation: getSjecMemberType(session?.user.email!), + name: session?.user.name!, + email: session?.user.email!, + phone: "", + entityName: "", + couponCode: "", + foodPreference: "veg", + photo: "", + idCard: "", + }, + }); + + const handlePayment = async () => { + setIsProcessing(true); + const couponCode = form.getValues("couponCode"); + + try { + // Create the order on the backend + const response = await fetch("/api/create-order", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ amount: pricing.finalPrice }), + }); + + if (!response.ok) { + throw new Error("Failed to create the order. Please try again."); + } + + const data = await response.json(); + + // Razorpay options + const options = { + key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID, + amount: pricing.finalPrice * 100, + currency: "INR", + name: "TEDxSJEC", + description: "Registration Fee", + order_id: data.orderId, + handler: async (response: any) => { + try { + const verifyResponse = 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, + }), }); - if (!response.ok) { - throw new Error("Failed to create the order. Please try again."); + if (!verifyResponse.ok) { + throw new Error("Payment verification failed. Please try again."); } - const data = await response.json(); - - // Razorpay options - const options = { - key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID, - amount: pricing.finalPrice * 100, - currency: "INR", - name: "TEDxSJEC", - description: "Registration Fee", - order_id: data.orderId, - handler: async (response: any) => { - try { - const verifyResponse = 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, - }), - }); - - if (!verifyResponse.ok) { - throw new Error("Payment verification failed. Please try again."); - } - - const verificationData = await verifyResponse.json(); - - if (verificationData.isOk) { - // Process after successful payment verification - try { - if (uploadedFiles.length > 0) { - const allFiles = uploadedFiles.flatMap((file) => file.files); - - if (allFiles.length > 0) { - //sent only first file to the function - const uploadResult = await handleFileUploadToEdgeStore(allFiles); - - if (!uploadResult) { - toast.error( - "Error uploading files. Please contact support from the contact us button at buttom right corner." - ); - } else { - toast.success("✅ Files uploaded successfully!"); - } - } else { - toast.warning("⚠️ No files to upload."); - } - } - - // Invalidate coupon if present - if (couponCode) { - const couponResult = await invalidateCouponCode(couponCode, session!); - if (!couponResult.success) { - toast.error(couponResult.message || "Error invalidating coupon."); - throw new Error("Coupon invalidation failed."); - } - } - - // Submit the form - try { - const formResponse = form.getValues(); - await submitForm(formResponse as FormDataInterface, pricing.finalPrice); - toast.success("✅ Form submitted successfully!"); - } catch (submitError) { - console.error("Form submission failed:", submitError); - toast.error("An error occurred while submitting the form."); - } - - // Mark success - setSuccess(true); - } catch (processError) { - console.error("Post-payment processing failed:", processError); - toast.error("An error occurred during post-payment processing."); - } - } else { - throw new Error( - "Payment verification failed: " + getErrorMessage(verificationData.error) - ); - } - } catch (handlerError: any) { - console.error( - "Payment verification or post-payment processing failed:", - handlerError - ); - toast.error(handlerError.message || "An error occurred during payment verification."); - } finally { - setIsProcessing(false); - } - }, - notes: { - customerName: form.getValues("name"), - customerEmail: session?.user?.email, - customerContact: form.getValues("phone"), - }, - prefill: { - name: form.getValues("name"), - email: session?.user?.email, - contact: form.getValues("phone"), - }, - theme: { - color: "#3399cc", - }, - modal: { - ondismiss: () => { - setIsProcessing(false); - toast.info("Payment process dismissed."); - }, - }, - }; + const verificationData = await verifyResponse.json(); + + if (verificationData.isOk) { + // Process after successful payment verification + try { + // Invalidate coupon if present + if (couponCode) { + const couponResult = await invalidateCouponCode( + couponCode, + session! + ); + if (!couponResult.success) { + toast.error( + couponResult.message || "Error invalidating coupon." + ); + throw new Error("Coupon invalidation failed."); + } + } - if (!process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID) { - throw new Error("Razorpay key is missing. Please configure it in your environment."); + // Submit the form + const formResponse = form.getValues(); + await submitForm( + formResponse as FormDataInterface, + pricing.finalPrice + ); + toast.success("✅ Form submitted successfully!"); + + // Mark success + setSuccess(true); + } catch (processError) { + console.error("Post-payment processing failed:", processError); + toast.error( + "An error occurred during post-payment processing." + ); + } + } else { + throw new Error( + "Payment verification failed: " + + getErrorMessage(verificationData.error) + ); } - - const rzp1 = new window.Razorpay(options); - rzp1.open(); - } catch (error: any) { - console.error("Payment initiation error:", error); - toast.error(`Payment error: ${getErrorMessage(error)}`); + } catch (handlerError: any) { + console.error( + "Payment verification or post-payment processing failed:", + handlerError + ); + toast.error( + handlerError.message || + "An error occurred during payment verification." + ); + } finally { setIsProcessing(false); - } - }; - - const onSubmit = async (values: FormSchema) => { - await handlePayment(); - }; - - const verifyCoupon = async () => { - const couponCode = form.getValues("couponCode"); - if (!couponCode) { - return; - } - if (couponCode.length !== 10) { - toast.error("Invalid Coupon Code"); - return; - } - try { - const result = await getPrice(couponCode); - if (!result.success) { - toast.error(result.message || "An error occurred while applying the coupon"); - return; - } - const { basePrice, discountAmount, finalPrice } = result; - setPricing({ - basePrice: basePrice ?? pricing.basePrice, - discountAmount: discountAmount ?? pricing.discountAmount, - finalPrice: finalPrice ?? pricing.finalPrice, - }); - toast.success("Coupon applied successfully"); - } catch (e) { - console.error(e); - toast.error(getErrorMessage(e)); - } - }; + } + }, + notes: { + name: form.getValues("name"), + email: session?.user?.email, + contact: form.getValues("phone"), + designation: form.getValues("designation"), + foodPreference: form.getValues("foodPreference"), + couponCode: couponCode, + entityName: form.getValues("entityName"), + memberType: memberType, + photo: form.getValues("photo"), + idCard: form.getValues("idCard"), + createdBy: session?.user?.id, + createdAt: new Date().toISOString(), + amount: pricing.finalPrice, + }, + prefill: { + name: form.getValues("name"), + email: session?.user?.email, + contact: form.getValues("phone"), + }, + theme: { + color: "#3399cc", + }, + modal: { + ondismiss: () => { + setIsProcessing(false); + toast.info("Payment process dismissed."); + }, + }, + }; - const handleNext = async () => { - let isValid = false; - if (step === 1) { - isValid = await form.trigger(["designation", "foodPreference", "name"]); - } else if (step === 2) { - const designation = form.getValues("designation"); - if (designation === "student") { - form.clearErrors(); - const validationResult = await studentFormSchema.safeParseAsync(form.getValues()); - isValid = validationResult.success; - if (!isValid) { - if (validationResult.error) { - validationResult.error.issues.forEach((issue) => { - form.setError(issue.path[0] as keyof FormSchema, { - type: "manual", - message: issue.message, - }); - }); - } - } - } else { - isValid = await form.trigger(["name", "email", "phone", "photo"]); - } - } + if (!process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID) { + throw new Error( + "Razorpay key is missing. Please configure it in your environment." + ); + } + + const rzp1 = new window.Razorpay(options); + rzp1.open(); + } catch (error: any) { + console.error("Payment initiation error:", error); + toast.error(`Payment error: ${getErrorMessage(error)}`); + setIsProcessing(false); + } + }; - if (isValid) { - setStep(step + 1); - } - }; + const onSubmit = async (values: FormSchema) => { + await handlePayment(); + }; - if (isProcessing) { - return ( -
- -
+ const verifyCoupon = async () => { + const couponCode = form.getValues("couponCode"); + if (!couponCode) { + return; + } + if (couponCode.length !== 10) { + toast.error("Invalid Coupon Code"); + return; + } + try { + const result = await getPrice(couponCode); + if (!result.success) { + toast.error( + result.message || "An error occurred while applying the coupon" ); + return; + } + const { basePrice, discountAmount, finalPrice } = result; + setPricing({ + basePrice: basePrice ?? pricing.basePrice, + discountAmount: discountAmount ?? pricing.discountAmount, + finalPrice: finalPrice ?? pricing.finalPrice, + }); + toast.success("Coupon applied successfully"); + } catch (e) { + console.error(e); + toast.error(getErrorMessage(e)); } - - if (success) { - return ( -
- -
+ }; + + const handleNext = async () => { + let isValid = false; + if (step === 1) { + isValid = await form.trigger(["designation", "foodPreference", "name"]); + } else if (step === 2) { + const designation = form.getValues("designation"); + if (designation === "student") { + form.clearErrors(); + const validationResult = await studentFormSchema.safeParseAsync( + form.getValues() ); + isValid = validationResult.success; + if (!isValid) { + if (validationResult.error) { + validationResult.error.issues.forEach((issue) => { + form.setError(issue.path[0] as keyof FormSchema, { + type: "manual", + message: issue.message, + }); + }); + } + } + } else { + isValid = await form.trigger(["name", "email", "phone", "photo"]); + } } + if (isValid) { + setStep(step + 1); + } + }; + + if (isProcessing) { return ( - -