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
183 changes: 183 additions & 0 deletions app/api/companies/[slug]/notifications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { NextRequest, NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server'
import type { CompanyNotificationPreferences } from '@/types/company'

// GET - Fetch notification preferences
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ slug: string }> }
) {
try {
const supabase = await createClient()
const { slug } = await params

// Get current user
const {
data: { user },
error: authError,
} = await supabase.auth.getUser()

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

// Get company
const { data: company, error: companyError } = await supabase
.from('companies')
.select('id, email_new_registration, email_event_approved, email_event_rejected, email_team_member_joined, email_subscription_expiring')
.eq('slug', slug)
.single()

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

// Check if user is a member of the company
const { data: membership, error: membershipError } = await supabase
.from('company_members')
.select('role')
.eq('company_id', company.id)
.eq('user_id', user.id)
.eq('status', 'active')
.single()

if (membershipError || !membership) {
return NextResponse.json(
{ error: 'You are not a member of this company' },
{ status: 403 }
)
}

// Only owners and admins can view notification preferences
if (membership.role !== 'owner' && membership.role !== 'admin') {
return NextResponse.json(
{ error: 'Insufficient permissions' },
{ status: 403 }
)
}

const preferences: CompanyNotificationPreferences = {
email_new_registration: company.email_new_registration ?? true,
email_event_approved: company.email_event_approved ?? true,
email_event_rejected: company.email_event_rejected ?? true,
email_team_member_joined: company.email_team_member_joined ?? true,
email_subscription_expiring: company.email_subscription_expiring ?? true,
}

return NextResponse.json({ data: preferences }, { status: 200 })
} catch (error) {
console.error('Error fetching notification preferences:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}

// PUT - Update notification preferences
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ slug: string }> }
) {
try {
const supabase = await createClient()
const { slug } = await params

// Get current user
const {
data: { user },
error: authError,
} = await supabase.auth.getUser()

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

// Get company
const { data: company, error: companyError } = await supabase
.from('companies')
.select('id')
.eq('slug', slug)
.single()

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

// Check if user is a member of the company
const { data: membership, error: membershipError } = await supabase
.from('company_members')
.select('role')
.eq('company_id', company.id)
.eq('user_id', user.id)
.eq('status', 'active')
.single()

if (membershipError || !membership) {
return NextResponse.json(
{ error: 'You are not a member of this company' },
{ status: 403 }
)
}

// Only owners and admins can update notification preferences
if (membership.role !== 'owner' && membership.role !== 'admin') {
return NextResponse.json(
{ error: 'Insufficient permissions' },
{ status: 403 }
)
}

// Parse request body
const body = await request.json()
const preferences: Partial<CompanyNotificationPreferences> = {}

// Validate and sanitize input
if (typeof body.email_new_registration === 'boolean') {
preferences.email_new_registration = body.email_new_registration
}
if (typeof body.email_event_approved === 'boolean') {
preferences.email_event_approved = body.email_event_approved
}
if (typeof body.email_event_rejected === 'boolean') {
preferences.email_event_rejected = body.email_event_rejected
}
if (typeof body.email_team_member_joined === 'boolean') {
preferences.email_team_member_joined = body.email_team_member_joined
}
if (typeof body.email_subscription_expiring === 'boolean') {
preferences.email_subscription_expiring = body.email_subscription_expiring
}

// Update company notification preferences
const { error: updateError } = await supabase
.from('companies')
.update({
...preferences,
updated_at: new Date().toISOString(),
})
.eq('id', company.id)

if (updateError) {
console.error('Error updating notification preferences:', updateError)
return NextResponse.json(
{ error: 'Failed to update notification preferences' },
{ status: 500 }
)
}

return NextResponse.json(
{
message: 'Notification preferences updated successfully',
data: preferences,
},
{ status: 200 }
)
} catch (error) {
console.error('Error updating notification preferences:', error)
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
)
}
}
82 changes: 74 additions & 8 deletions app/dashboard/company/[slug]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export default function CompanySettingsPage() {
email_team_member_joined: true,
email_subscription_expiring: true,
})
const [loadingNotifications, setLoadingNotifications] = useState(false)
const [savingNotifications, setSavingNotifications] = useState(false)

// Update form data when company changes
React.useEffect(() => {
Expand Down Expand Up @@ -105,6 +107,28 @@ export default function CompanySettingsPage() {
}
}, [currentCompany])

// Fetch notification preferences when company changes
React.useEffect(() => {
const fetchNotificationPreferences = async () => {
if (!currentCompany) return

setLoadingNotifications(true)
try {
const response = await fetch(`/api/companies/${currentCompany.slug}/notifications`)
if (response.ok) {
const result = await response.json()
setNotificationPrefs(result.data)
}
} catch (error) {
console.error('Error fetching notification preferences:', error)
} finally {
setLoadingNotifications(false)
}
}

fetchNotificationPreferences()
}, [currentCompany])

if (contextLoading || isPendingInvitation) {
return (
<div className="flex items-center justify-center min-h-[60vh]">
Expand Down Expand Up @@ -944,7 +968,12 @@ export default function CompanySettingsPage() {
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-4">
{loadingNotifications ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
) : (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="email_new_registration" className="text-zinc-200">
Expand Down Expand Up @@ -1054,18 +1083,55 @@ export default function CompanySettingsPage() {
/>
</div>
</div>
)}

<div className="flex justify-end">
<Button
onClick={() => {
toast({
title: 'Success',
description: 'Notification preferences saved',
})
onClick={async () => {
if (!currentCompany) return

setSavingNotifications(true)
try {
const response = await fetch(`/api/companies/${currentCompany.slug}/notifications`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(notificationPrefs),
})

if (!response.ok) {
throw new Error('Failed to save notification preferences')
}

toast({
title: 'Success',
description: 'Notification preferences saved successfully',
})
} catch (error) {
console.error('Error saving notification preferences:', error)
toast({
title: 'Error',
description: 'Failed to save notification preferences',
variant: 'destructive',
})
} finally {
setSavingNotifications(false)
}
}}
disabled={savingNotifications || loadingNotifications}
>
<Save className="mr-2 h-4 w-4" />
Save Preferences
{savingNotifications ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : (
<>
<Save className="mr-2 h-4 w-4" />
Save Preferences
</>
)}
</Button>
</div>
</CardContent>
Expand Down
Loading
Loading