diff --git a/app/api/companies/register/route.ts b/app/api/companies/register/route.ts index 037a5a5b..22e4ee55 100644 --- a/app/api/companies/register/route.ts +++ b/app/api/companies/register/route.ts @@ -96,8 +96,56 @@ export async function POST(request: NextRequest) { socials: body.socials as Record | undefined, } - // Create company - const company = await companyService.createCompany(registrationData, user.id) + // Check if this is a resubmission (updating existing rejected company) + const companyId = body.companyId as string | undefined + let company + + if (companyId) { + // Resubmission flow - update existing company + const { data: existingCompany, error: fetchError } = await supabase + .from('companies') + .select('*') + .eq('id', companyId) + .single() + + if (fetchError || !existingCompany) { + return NextResponse.json( + { error: 'Company not found for resubmission' }, + { status: 404 } + ) + } + + // Verify the user is the company owner + if (existingCompany.owner_id !== user.id) { + return NextResponse.json( + { error: 'Forbidden: You are not the owner of this company' }, + { status: 403 } + ) + } + + // Verify company is in rejected status + if (existingCompany.verification_status !== 'rejected') { + return NextResponse.json( + { error: 'Company is not in rejected status and cannot be resubmitted' }, + { status: 400 } + ) + } + + // Update company with new data and reset verification status + const updateData: Partial = { + ...registrationData, + verification_status: 'pending' as const, + verification_notes: null, + verified_by: null, + updated_at: new Date().toISOString(), + } + + company = await companyService.updateCompany(companyId, updateData) + } else { + // New registration flow + company = await companyService.createCompany(registrationData, user.id) + } + // Upload verification documents if provided let uploadedDocuments: string[] = [] diff --git a/app/api/companies/resubmit/[id]/route.ts b/app/api/companies/resubmit/[id]/route.ts new file mode 100644 index 00000000..b1241ecf --- /dev/null +++ b/app/api/companies/resubmit/[id]/route.ts @@ -0,0 +1,111 @@ +// API route to fetch company data for resubmission after rejection +import { NextRequest, NextResponse } from 'next/server' +import { createClient } from '@/lib/supabase/server' + +/** + * GET /api/companies/resubmit/[id] + * Fetch company data for resubmission (owner only) + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const supabase = await createClient() + const { id: companyId } = await params + + // Check authentication + const { + data: { user }, + error: authError, + } = await supabase.auth.getUser() + + if (authError || !user) { + return NextResponse.json( + { success: false, error: 'Unauthorized' }, + { status: 401 } + ) + } + + // Get company details + const { data: company, error: companyError } = await supabase + .from('companies') + .select('*') + .eq('id', companyId) + .single() + + if (companyError || !company) { + return NextResponse.json( + { success: false, error: 'Company not found' }, + { status: 404 } + ) + } + + // Verify the user is the company owner + console.log('🔍 Ownership verification:', { + userId: user.id, + companyOwnerId: company.owner_id, + match: company.owner_id === user.id, + companyId: company.id, + companyName: company.name + }); + + if (company.owner_id !== user.id) { + return NextResponse.json( + { success: false, error: 'Forbidden: You are not the owner of this company' }, + { status: 403 } + ) + } + // Only allow resubmission for rejected companies + if (company.verification_status !== 'rejected') { + return NextResponse.json( + { + success: false, + error: 'Company is not in rejected status', + currentStatus: company.verification_status + }, + { status: 400 } + ) + } + + // Return company data for form pre-population + return NextResponse.json({ + success: true, + company: { + id: company.id, + name: company.name, + legal_name: company.legal_name, + email: company.email, + phone: company.phone, + website: company.website, + industry: company.industry, + company_size: company.company_size, + description: company.description, + // Construct address object from individual columns + address: { + street: company.address_street || '', + city: company.address_city || '', + state: company.address_state || '', + country: company.address_country || '', + zip: company.address_zip || '', + }, + // Construct socials object from individual columns + socials: { + linkedin: company.linkedin_url || '', + twitter: company.twitter_url || '', + facebook: company.facebook_url || '', + instagram: company.instagram_url || '', + }, + verification_status: company.verification_status, + verification_notes: company.verification_notes, + logo_url: company.logo_url, + }, + }) + } catch (error) { + console.error('Unexpected error in GET /api/companies/resubmit/[id]:', error) + return NextResponse.json( + { success: false, error: 'Internal server error' }, + { status: 500 } + ) + } +} diff --git a/app/companies/register/page.tsx b/app/companies/register/page.tsx index 6244ef9d..5cb63545 100644 --- a/app/companies/register/page.tsx +++ b/app/companies/register/page.tsx @@ -1,20 +1,54 @@ "use client"; -import { useState } from "react"; -import { useRouter } from "next/navigation"; +import { useState, useEffect, Suspense } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { motion } from "framer-motion"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { CompanyRegistrationForm } from "@/components/companies/CompanyRegistrationForm"; import { useAuth } from "@/lib/hooks/useAuth"; -import { Building2, CheckCircle2, Clock, Mail, ArrowRight } from "lucide-react"; +import { Building2, CheckCircle2, Clock, Mail, ArrowRight, AlertCircle } from "lucide-react"; import Link from "next/link"; +import { toast } from "sonner"; -export default function CompanyRegisterPage() { +interface CompanyData { + id: string; + name: string; + email: string; + legal_name?: string; + phone?: string; + website: string; + industry: string; + company_size: string; + description: string; + address?: { + street: string; + city: string; + state: string; + country: string; + zip: string; + }; + socials?: { + linkedin: string; + twitter: string; + facebook: string; + instagram: string; + }; + verification_status: string; + verification_notes?: string; + logo_url?: string; +} + +function CompanyRegisterContent() { const { user, loading: authLoading } = useAuth(); const router = useRouter(); + const searchParams = useSearchParams(); const [registrationComplete, setRegistrationComplete] = useState(false); const [companyData, setCompanyData] = useState<{ name: string; email: string } | null>(null); + const [resubmitData, setResubmitData] = useState(null); + const [loadingResubmit, setLoadingResubmit] = useState(false); + const [resubmitError, setResubmitError] = useState(null); + const resubmitId = searchParams.get("resubmit"); const handleSuccess = (company: unknown) => { const companyObj = company as { name: string; email: string }; @@ -26,8 +60,50 @@ export default function CompanyRegisterPage() { console.error("Registration error:", error); }; - // Show loading state while checking authentication - if (authLoading) { + // Fetch company data for resubmission + useEffect(() => { + const fetchResubmitData = async () => { + if (!resubmitId) { + console.log('⏭️ No resubmit ID'); + return; + } + + if (!user) { + console.log('⏭️ No user yet, waiting for auth...'); + return; + } + + console.log('🔄 Fetching resubmit data for company:', resubmitId); + setLoadingResubmit(true); + setResubmitError(null); + try { + const response = await fetch(`/api/companies/resubmit/${resubmitId}`); + console.log('📡 Response status:', response.status); + const result = await response.json(); + console.log('📦 Response data:', result); + + if (!response.ok) { + throw new Error(result.error || "Failed to fetch company data"); + } + + setResubmitData(result.company); + console.log('✅ Successfully loaded company data'); + } catch (error) { + console.error("❌ Error fetching resubmit data:", error); + const errorMessage = error instanceof Error ? error.message : "Failed to load company data"; + setResubmitError(errorMessage); + toast.error(errorMessage); + } finally { + setLoadingResubmit(false); + console.log('🏁 Fetch complete'); + } + }; + + fetchResubmitData(); + }, [resubmitId, user]); + + // Show loading state while checking authentication or loading resubmit data + if (authLoading || loadingResubmit) { return (
@@ -40,7 +116,53 @@ export default function CompanyRegisterPage() {
-

Loading...

+

+ {loadingResubmit ? "Loading company data..." : "Loading..."} +

+
+
+ + +
+
+ ); + } + + // Show error state if resubmit data fetch failed + if (resubmitId && resubmitError && !loadingResubmit) { + return ( +
+
+ + + +
+ +
+ Access Denied + + {resubmitError} + +
+ +

+ You can only update companies that you own. If you believe this is an error, + please contact support. +

+
+ +
@@ -109,9 +231,11 @@ export default function CompanyRegisterPage() {
- Registration Successful! + + {resubmitData ? "Resubmission Successful!" : "Registration Successful!"} + - Your company registration has been submitted for review + Your company {resubmitData ? "has been resubmitted" : "registration has been submitted"} for review @@ -230,44 +354,75 @@ export default function CompanyRegisterPage() {
-

Register Your Company

+

+ {resubmitData ? "Update Company Information" : "Register Your Company"} +

- Join Codeunia's marketplace and start hosting hackathons and events for the developer community + {resubmitData + ? "Update your company information and resubmit for verification" + : "Join Codeunia's marketplace and start hosting hackathons and events for the developer community" + }

- {/* Benefits */} - - -

Why host events on Codeunia?

-
-
- -
-

Reach Developers

-

Connect with thousands of talented developers

+ {/* Rejection Feedback Banner */} + {resubmitData?.verification_notes && ( + + +
+ +
+ + Verification Feedback + + + {resubmitData.verification_notes} +
-
- -
-

Easy Management

-

Intuitive dashboard for event and team management

+ + + )} + + {/* Benefits */} + {!resubmitData && ( + + +

Why host events on Codeunia?

+
+
+ +
+

Reach Developers

+

Connect with thousands of talented developers

+
-
-
- -
-

Analytics & Insights

-

Track engagement and measure event success

+
+ +
+

Easy Management

+

Intuitive dashboard for event and team management

+
+
+
+ +
+

Analytics & Insights

+

Track engagement and measure event success

+
-
-
-
+ + + )} {/* Registration Form */} - + {/* Help Text */} @@ -289,3 +444,24 @@ export default function CompanyRegisterPage() {
); } + +export default function CompanyRegisterPage() { + return ( + +
+ + +
+
+

Loading...

+
+
+
+
+
+ }> + + + ); +} diff --git a/components/companies/CompanyRegistrationForm.tsx b/components/companies/CompanyRegistrationForm.tsx index 0141b795..75c77d89 100644 --- a/components/companies/CompanyRegistrationForm.tsx +++ b/components/companies/CompanyRegistrationForm.tsx @@ -9,12 +9,12 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Progress } from "@/components/ui/progress"; -import { - Building2, - FileText, - CheckCircle2, - Upload, - X, +import { + Building2, + FileText, + CheckCircle2, + Upload, + X, Loader2, ArrowRight, ArrowLeft, @@ -33,6 +33,32 @@ import { HelpTooltip, CompanyHelpTooltips } from "@/components/help/HelpTooltip" interface CompanyRegistrationFormProps { onSuccess?: (company: unknown) => void; onError?: (error: Error) => void; + initialData?: CompanyData; + companyId?: string; +} + +interface CompanyData { + name: string; + legal_name?: string; + email: string; + phone?: string; + website: string; + industry: string; + company_size: string; + description: string; + address?: { + street: string; + city: string; + state: string; + country: string; + zip: string; + }; + socials?: { + linkedin: string; + twitter: string; + facebook: string; + instagram: string; + }; } interface FormData extends CompanyRegistrationData { @@ -60,26 +86,26 @@ const COMPANY_SIZES = [ { value: "enterprise", label: "Enterprise (1000+ employees)" } ]; -export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrationFormProps) { +export function CompanyRegistrationForm({ onSuccess, onError, initialData, companyId }: CompanyRegistrationFormProps) { const [currentStep, setCurrentStep] = useState(1); const [isSubmitting, setIsSubmitting] = useState(false); const [formData, setFormData] = useState({ - name: "", - legal_name: "", - email: "", - website: "", - industry: "", - company_size: "", - description: "", - phone: "", - address: { + name: initialData?.name || "", + legal_name: initialData?.legal_name || "", + email: initialData?.email || "", + website: initialData?.website || "", + industry: initialData?.industry || "", + company_size: initialData?.company_size || "", + description: initialData?.description || "", + phone: initialData?.phone || "", + address: initialData?.address || { street: "", city: "", state: "", country: "", zip: "" }, - socials: { + socials: initialData?.socials || { linkedin: "", twitter: "", facebook: "", @@ -108,24 +134,24 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat const handleFileChange = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); - + // Validate file types const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png']; const invalidFiles = files.filter(file => !allowedTypes.includes(file.type)); - + if (invalidFiles.length > 0) { toast.error("Only PDF, JPEG, and PNG files are allowed"); return; } - + // Validate file sizes (max 5MB each) const oversizedFiles = files.filter(file => file.size > 5 * 1024 * 1024); - + if (oversizedFiles.length > 0) { toast.error("Each file must be less than 5MB"); return; } - + setFormData((prev) => ({ ...prev, verification_documents: [...prev.verification_documents, ...files] @@ -155,17 +181,17 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat return false; } return true; - + case 2: if (!formData.description || formData.description.length < 50) { toast.error("Description must be at least 50 characters"); return false; } return true; - + case 3: return true; // Verification documents are optional - + default: return true; } @@ -183,7 +209,7 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (!validateStep(currentStep)) { return; } @@ -193,7 +219,7 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat try { // Prepare FormData for file upload const submitData = new FormData(); - + // Add company data submitData.append("name", formData.name); submitData.append("email", formData.email); @@ -201,20 +227,25 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat submitData.append("industry", formData.industry); submitData.append("company_size", formData.company_size); submitData.append("description", formData.description); - + + // Add companyId if resubmitting + if (companyId) { + submitData.append("companyId", companyId); + } + if (formData.legal_name) submitData.append("legal_name", formData.legal_name); if (formData.phone) submitData.append("phone", formData.phone); - + // Add address if any field is filled if (formData.address && Object.values(formData.address).some(v => v)) { submitData.append("address", JSON.stringify(formData.address)); } - + // Add socials if any field is filled if (formData.socials && Object.values(formData.socials).some(v => v)) { submitData.append("socials", JSON.stringify(formData.socials)); } - + // Add verification documents formData.verification_documents.forEach((file, index) => { submitData.append(`verification_document_${index}`, file); @@ -231,8 +262,12 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat throw new Error(result.error || "Failed to register company"); } - toast.success("Company registered successfully! We'll review your application within 48 hours."); - + toast.success( + companyId + ? "Company information updated successfully! We'll review your resubmission within 48 hours." + : "Company registered successfully! We'll review your application within 48 hours." + ); + if (onSuccess) { onSuccess(result.company); } @@ -240,7 +275,7 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat console.error("Error submitting form:", error); const errorMessage = error instanceof Error ? error.message : "Failed to register company"; toast.error(errorMessage); - + if (onError) { onError(error instanceof Error ? error : new Error(errorMessage)); } @@ -633,7 +668,7 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat

- Recommended documents: Business registration certificate, + Recommended documents: Business registration certificate, tax ID, or other official documents that verify your company's legitimacy.

@@ -656,7 +691,7 @@ export function CompanyRegistrationForm({ onSuccess, onError }: CompanyRegistrat Previous )} - + {currentStep < totalSteps ? (