Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions app/api/companies/[slug]/members/check-invitation/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import { companyService } from '@/lib/services/company-service'

// Force Node.js runtime for API routes
export const runtime = 'nodejs'

/**
* GET /api/companies/[slug]/members/check-invitation
* Check if the current user has an invitation to this company
* Requires authentication
*/
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ slug: string }> }
) {
try {
const { slug } = await params

// Check authentication
const supabase = await createClient()
const {
data: { user },
error: authError,
} = await supabase.auth.getUser()

if (authError || !user) {
return NextResponse.json(
{ success: false, error: 'Unauthorized: Authentication required' },
{ status: 401 }
)
}

// Get company
const company = await companyService.getCompanyBySlug(slug)

if (!company) {
return NextResponse.json(
{
success: false,
error: 'Company not found',
},
{ status: 404 }
)
}

// Check if user has a membership (any status)
const { data: membership, error: membershipError } = await supabase
.from('company_members')
.select('*')
.eq('company_id', company.id)
.eq('user_id', user.id)
.single()

if (membershipError) {
if (membershipError.code === 'PGRST116') {
// No membership found
return NextResponse.json({
success: true,
membership: null,
})
}
throw membershipError
}

return NextResponse.json({
success: true,
membership,
})
} catch (error) {
console.error('Error in GET /api/companies/[slug]/members/check-invitation:', error)

return NextResponse.json(
{
success: false,
error: 'Internal server error',
},
{ status: 500 }
)
}
}
15 changes: 13 additions & 2 deletions app/auth/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,25 @@ export default function ForgotPasswordPage() {
})

if (error) {
throw error
console.error("Supabase error:", error)

// Show specific error messages
if (error.message.includes("Email") || error.message.includes("SMTP")) {
toast.error("Email service not configured. Please contact support.")
} else if (error.message.includes("rate limit")) {
toast.error("Too many requests. Please try again later.")
} else {
toast.error(error.message || "Failed to send reset link. Please try again.")
}
return
}

toast.success("Password reset link sent! Check your email.")
setFormData({ email: "" })
} catch (error) {
console.error("Error sending reset link:", error)
toast.error("Failed to send reset link. Please try again.")
const errorMessage = error instanceof Error ? error.message : "Failed to send reset link. Please try again."
toast.error(errorMessage)
} finally {
setIsLoading(false)
}
Expand Down
13 changes: 6 additions & 7 deletions app/dashboard/company/[slug]/accept-invitation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@ export default function AcceptInvitationPage() {
setCompanyName(companyData.company?.name || companySlug)

// Check if user has a pending invitation
const membersResponse = await fetch(`/api/companies/${companySlug}/members`)
if (!membersResponse.ok) {
throw new Error('Failed to check invitation status')
const invitationResponse = await fetch(`/api/companies/${companySlug}/members/check-invitation`)
if (!invitationResponse.ok) {
const errorData = await invitationResponse.json()
throw new Error(errorData.error || 'Failed to check invitation status')
}
const membersData = await membersResponse.json()
const members = membersData.members || []

const userMembership = members.find((m: { user_id: string; status: string }) => m.user_id === user.id)
const invitationData = await invitationResponse.json()
const userMembership = invitationData.membership

if (!userMembership) {
setError('No invitation found for your account')
Expand Down
167 changes: 167 additions & 0 deletions app/dashboard/company/[slug]/events/[eventSlug]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
'use client'

import { useState, useEffect, useCallback } from 'react'
import { useRouter, useParams } from 'next/navigation'
import { useCompanyContext } from '@/contexts/CompanyContext'
import { EventForm } from '@/components/dashboard/EventForm'
import { ArrowLeft, Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'
import { Event } from '@/types/events'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from '@/components/ui/alert-dialog'

export default function EditEventPage() {
const router = useRouter()
const params = useParams()
const slug = params.slug as string
const { currentCompany, loading: companyLoading } = useCompanyContext()
const [event, setEvent] = useState<Event | null>(null)
const [loading, setLoading] = useState(true)
const [deleting, setDeleting] = useState(false)

const fetchEvent = useCallback(async () => {
try {
setLoading(true)
const response = await fetch(`/api/events/${slug}`)

if (!response.ok) {
throw new Error('Failed to fetch event')
}

const data = await response.json()
setEvent(data.event)
} catch (error) {
console.error('Error fetching event:', error)
toast.error('Failed to load event')
router.push('/dashboard/company/events')
} finally {
setLoading(false)
}
}, [slug, router])

useEffect(() => {
if (slug) {
fetchEvent()
}
}, [slug, fetchEvent])

const handleSuccess = (updatedEvent: Event) => {
setEvent(updatedEvent)
toast.success('Event updated successfully!')
}

const handleDelete = async () => {
try {
setDeleting(true)
const response = await fetch(`/api/events/${slug}`, {
method: 'DELETE',
})

if (!response.ok) {
throw new Error('Failed to delete event')
}

toast.success('Event deleted successfully!')
router.push('/dashboard/company/events')
} catch (error) {
console.error('Error deleting event:', error)
toast.error('Failed to delete event')
} finally {
setDeleting(false)
}
}

if (loading || companyLoading || !currentCompany) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
)
}

if (!event) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<h2 className="text-2xl font-bold mb-2">Event not found</h2>
<p className="text-muted-foreground mb-4">
The event you&apos;re looking for doesn&apos;t exist or you don&apos;t have access to it.
</p>
<Link href="/dashboard/company/events">
<Button>Back to Events</Button>
</Link>
</div>
</div>
)
}

return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/dashboard/company/events">
<Button variant="outline" size="sm">
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Events
</Button>
</Link>
<div>
<h1 className="text-3xl font-bold tracking-tight">Edit Event</h1>
<p className="text-muted-foreground mt-1">
Update your event details
</p>
</div>
</div>

{/* Delete Button */}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline" className="text-red-600 border-red-600 hover:bg-red-50">
<Trash2 className="h-4 w-4 mr-2" />
Delete Event
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete the event
&quot;{event.title}&quot; and all associated data.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={handleDelete}
disabled={deleting}
className="bg-red-600 hover:bg-red-700"
>
{deleting ? 'Deleting...' : 'Delete Event'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>

{/* Event Form */}
<EventForm
company={currentCompany}
event={event}
mode="edit"
onSuccess={handleSuccess}
/>
</div>
)
}
53 changes: 53 additions & 0 deletions app/dashboard/company/[slug]/events/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use client'

import { useRouter } from 'next/navigation'
import { useCompanyContext } from '@/contexts/CompanyContext'
import { EventForm } from '@/components/dashboard/EventForm'
import { ArrowLeft } from 'lucide-react'
import { Button } from '@/components/ui/button'
import Link from 'next/link'

export default function CreateEventPage() {
const router = useRouter()
const { currentCompany, loading } = useCompanyContext()

const handleSuccess = () => {
// Redirect to events list after successful creation
router.push('/dashboard/company/events')
}

if (loading || !currentCompany) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
)
}

return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4">
<Link href="/dashboard/company/events">
<Button variant="outline" size="sm">
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Events
</Button>
</Link>
<div>
<h1 className="text-3xl font-bold tracking-tight">Create Event</h1>
<p className="text-muted-foreground mt-1">
Create a new event for your company
</p>
</div>
</div>

{/* Event Form */}
<EventForm
company={currentCompany}
mode="create"
onSuccess={handleSuccess}
/>
</div>
)
}
Loading
Loading