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
2 changes: 1 addition & 1 deletion app/api/admin/hackathons/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function PUT(request: NextRequest) {
return NextResponse.json({ error: 'Hackathon data is required' }, { status: 400 })
}

const hackathon = await hackathonsService.updateHackathon(slug, data)
const hackathon = await hackathonsService.updateHackathon(slug, data, user.id)

return NextResponse.json(hackathon)
} catch (error) {
Expand Down
61 changes: 53 additions & 8 deletions app/api/admin/moderation/events/[id]/approve/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,58 @@ export async function POST(
notes
)

// Send notification email to company
if (approvedEvent.company && approvedEvent.company.email) {
const emailContent = getEventApprovedEmail({
eventTitle: approvedEvent.title,
companyName: approvedEvent.company.name,
eventUrl: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/events/${approvedEvent.slug}`,
publishDate: new Date().toLocaleDateString(),
notes: notes || '',
// Get creator's email from profiles table
let creatorEmail: string | null = null
let creatorName: string | null = null

if (approvedEvent.created_by) {
const { createClient } = await import('@/lib/supabase/server')
const supabase = await createClient()
const { data: creatorProfile } = await supabase
.from('profiles')
.select('email, first_name, last_name')
.eq('id', approvedEvent.created_by)
.single()

if (creatorProfile) {
creatorEmail = creatorProfile.email
creatorName = creatorProfile.first_name
? `${creatorProfile.first_name} ${creatorProfile.last_name || ''}`.trim()
: null
}
}

// Prepare email content
const emailContent = getEventApprovedEmail({
eventTitle: approvedEvent.title,
companyName: approvedEvent.company?.name || 'Your Company',
eventUrl: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/events/${approvedEvent.slug}`,
publishDate: new Date().toLocaleDateString(),
notes: notes || '',
creatorName: creatorName || undefined,
})

// Send notification email to event creator (primary)
if (creatorEmail) {
console.log(`📧 Sending event approval email to creator: ${creatorEmail}`)
await sendEmail({
to: creatorEmail,
subject: emailContent.subject,
html: emailContent.html,
}).catch(error => {
console.error('❌ Failed to send approval email to creator:', error)
})
}

// Also send to company email if different from creator
if (approvedEvent.company?.email && approvedEvent.company.email !== creatorEmail) {
console.log(`📧 Sending event approval email to company: ${approvedEvent.company.email}`)
await sendEmail({
to: approvedEvent.company.email,
subject: emailContent.subject,
html: emailContent.html,
}).catch(error => {
console.error('❌ Failed to send approval email to company:', error)
})
}

Expand Down Expand Up @@ -90,12 +128,19 @@ function getEventApprovedEmail(params: {
eventUrl: string
publishDate: string
notes: string
creatorName?: string
}) {
const greeting = params.creatorName ? `Hi ${params.creatorName},` : 'Hello,'

const content = `
<h2 style="margin: 0 0 20px 0; color: #111827; font-size: 20px;">
🎉 Your Event is Live!
</h2>

<p style="margin: 0 0 15px 0; color: #374151; font-size: 16px; line-height: 1.5;">
${greeting}
</p>

<p style="margin: 0 0 15px 0; color: #374151; font-size: 16px; line-height: 1.5;">
Great news! Your event has been approved and is now live on CodeUnia.
</p>
Expand Down
69 changes: 57 additions & 12 deletions app/api/admin/moderation/events/[id]/reject/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,58 @@ export async function POST(
reason
)

// Send notification email to company
if (rejectedEvent.company && rejectedEvent.company.email) {
const emailContent = getEventRejectedEmail({
eventTitle: rejectedEvent.title,
companyName: rejectedEvent.company.name,
rejectionReason: reason,
editUrl: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/dashboard/company/events/${rejectedEvent.slug}/edit`,
guidelines: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/guidelines`,
// Get creator's email from profiles table
let creatorEmail: string | null = null
let creatorName: string | null = null

if (rejectedEvent.created_by) {
const { createClient } = await import('@/lib/supabase/server')
const supabase = await createClient()
const { data: creatorProfile } = await supabase
.from('profiles')
.select('email, first_name, last_name')
.eq('id', rejectedEvent.created_by)
.single()

if (creatorProfile) {
creatorEmail = creatorProfile.email
creatorName = creatorProfile.first_name
? `${creatorProfile.first_name} ${creatorProfile.last_name || ''}`.trim()
: null
}
}

// Prepare email content
const emailContent = getEventRejectedEmail({
eventTitle: rejectedEvent.title,
companyName: rejectedEvent.company?.name || 'Your Company',
rejectionReason: reason,
editUrl: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/dashboard/company/${rejectedEvent.company?.slug}/events`,
guidelines: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/guidelines`,
creatorName: creatorName || undefined,
})

// Send notification email to event creator (primary)
if (creatorEmail) {
console.log(`📧 Sending event rejection email to creator: ${creatorEmail}`)
await sendEmail({
to: creatorEmail,
subject: emailContent.subject,
html: emailContent.html,
}).catch(error => {
console.error('❌ Failed to send rejection email to creator:', error)
})
}

// Also send to company email if different from creator
if (rejectedEvent.company?.email && rejectedEvent.company.email !== creatorEmail) {
console.log(`📧 Sending event rejection email to company: ${rejectedEvent.company.email}`)
await sendEmail({
to: rejectedEvent.company.email,
subject: emailContent.subject,
html: emailContent.html,
}).catch(error => {
console.error('❌ Failed to send rejection email to company:', error)
})
}

Expand Down Expand Up @@ -100,14 +138,21 @@ function getEventRejectedEmail(params: {
rejectionReason: string
editUrl: string
guidelines: string
creatorName?: string
}) {
const greeting = params.creatorName ? `Hi ${params.creatorName},` : 'Hello,'

const content = `
<h2 style="margin: 0 0 20px 0; color: #111827; font-size: 20px;">
Event Review Update
</h2>

<p style="margin: 0 0 15px 0; color: #374151; font-size: 16px; line-height: 1.5;">
Thank you for submitting your event to CodeUnia. After review, we're unable to approve your event at this time.
${greeting}
</p>

<p style="margin: 0 0 15px 0; color: #374151; font-size: 16px; line-height: 1.5;">
Thank you for submitting your event to Codeunia. After review, we're unable to approve your event at this time.
</p>

<div style="background-color: #fef2f2; border-left: 4px solid #ef4444; padding: 15px; margin: 20px 0; border-radius: 4px;">
Expand Down Expand Up @@ -158,7 +203,7 @@ function getEventRejectedEmail(params: {
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event Review Update - CodeUnia</title>
<title>Event Review Update - Codeunia</title>
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f5f5f5; padding: 20px;">
Expand All @@ -167,7 +212,7 @@ function getEventRejectedEmail(params: {
<table width="600" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<tr>
<td style="background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); padding: 30px; text-align: center;">
<h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: bold;">CodeUnia</h1>
<h1 style="margin: 0; color: #ffffff; font-size: 24px; font-weight: bold;">Codeunia</h1>
</td>
</tr>
<tr>
Expand All @@ -181,7 +226,7 @@ function getEventRejectedEmail(params: {
Need help? Visit our <a href="${process.env.NEXT_PUBLIC_SITE_URL || 'https://codeunia.com'}/protected/help" style="color: #3b82f6; text-decoration: none;">Help Center</a>
</p>
<p style="margin: 0; color: #9ca3af; font-size: 12px;">
© ${new Date().getFullYear()} CodeUnia. All rights reserved.
© ${new Date().getFullYear()} Codeunia. All rights reserved.
</p>
</td>
</tr>
Expand Down
43 changes: 42 additions & 1 deletion app/api/companies/[slug]/members/[userId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { companyMemberService } from '@/lib/services/company-member-service'
import { CompanyError } from '@/types/company'
import { UnifiedCache } from '@/lib/unified-cache-system'
import { z } from 'zod'
import { getRoleChangeEmail, sendCompanyEmail } from '@/lib/email/company-emails'
import { getRoleChangeEmail, getMemberRemovedEmail, sendCompanyEmail } from '@/lib/email/company-emails'

// Force Node.js runtime for API routes
export const runtime = 'nodejs'
Expand Down Expand Up @@ -263,9 +263,50 @@ export async function DELETE(
)
}

// Get member's profile information for email before removing
const { data: memberProfile } = await supabase
.from('profiles')
.select('email, first_name, last_name')
.eq('id', userId)
.single()

// Get requesting user's name for email
const { data: requestingUserProfile } = await supabase
.from('profiles')
.select('first_name, last_name')
.eq('id', user.id)
.single()

const removedByName = requestingUserProfile?.first_name
? `${requestingUserProfile.first_name} ${requestingUserProfile.last_name || ''}`.trim()
: 'a team administrator'

// Remove member
await companyMemberService.removeMember(targetMember.id)

// Send removal notification email
if (memberProfile?.email) {
const memberName = memberProfile.first_name || memberProfile.email.split('@')[0]

const emailContent = getMemberRemovedEmail({
memberName,
companyName: company.name,
removedBy: removedByName,
role: targetMember.role,
})

// Send email asynchronously (don't wait for it)
console.log(`📧 Sending member removal email to ${memberProfile.email}`)
sendCompanyEmail({
to: memberProfile.email,
subject: emailContent.subject,
html: emailContent.html,
}).catch(error => {
console.error('❌ Failed to send member removal email:', error)
// Don't fail the request if email fails
})
}

// Invalidate cache
await UnifiedCache.purgeByTags(['content', 'api'])

Expand Down
2 changes: 1 addition & 1 deletion app/api/hackathons/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export async function PUT(request: NextRequest, { params }: RouteContext) {
)
}

const hackathon = await hackathonsService.updateHackathon(id, hackathonData)
const hackathon = await hackathonsService.updateHackathon(id, hackathonData, user.id)

return NextResponse.json({ hackathon })
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion app/companies/[slug]/events/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default function CompanyEventsPage() {

{/* Company Header */}
{company && (
<section className="py-12 bg-gradient-to-b from-muted/30 to-background border-b border-primary/10">
<section className="pt-24 pb-12 bg-gradient-to-b from-muted/30 to-background border-b border-primary/10">
<div className="container px-4 mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down
2 changes: 1 addition & 1 deletion app/companies/[slug]/hackathons/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export default function CompanyHackathonsPage() {

{/* Company Header */}
{company && (
<section className="py-12 bg-gradient-to-b from-muted/30 to-background border-b border-primary/10">
<section className="pt-24 pb-12 bg-gradient-to-b from-muted/30 to-background border-b border-primary/10">
<div className="container px-4 mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down
2 changes: 1 addition & 1 deletion app/companies/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function CompanyProfilePage() {
<div className="flex flex-col min-h-screen bg-gradient-to-br from-background via-background to-muted/10">
<Header />

<main className="flex-1 py-12">
<main className="flex-1 pt-24 pb-12">
<div className="container px-4 mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down
4 changes: 2 additions & 2 deletions components/companies/CompanyProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ export function CompanyProfile({ company, isOwner = false, className }: CompanyP
<Card className="overflow-hidden">
{/* Banner */}
<div
className="h-48 bg-gradient-to-r from-primary/20 via-primary/10 to-primary/20"
className="h-56 sm:h-64 bg-gradient-to-r from-primary/20 via-primary/10 to-primary/20"
style={company.banner_url ? {
backgroundImage: `url(${company.banner_url})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
} : undefined}
/>

<CardHeader className="relative -mt-16 pb-4">
<CardHeader className="relative -mt-20 pb-4">
<div className="flex flex-col sm:flex-row items-start sm:items-end gap-4">
{/* Logo */}
<Avatar className="h-32 w-32 border-4 border-background shadow-xl">
Expand Down
8 changes: 8 additions & 0 deletions components/notifications/notification-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ export function getNotificationIcon(type: NotificationType): LucideIcon {
event_approved: CheckCircle2,
event_rejected: XCircle,
event_changes_requested: AlertCircle,
event_updated: AlertCircle,
event_status_changed: AlertCircle,
hackathon_approved: CheckCircle2,
hackathon_rejected: XCircle,
hackathon_changes_requested: AlertCircle,
hackathon_updated: AlertCircle,
hackathon_status_changed: AlertCircle,
new_event_registration: Calendar,
new_hackathon_registration: Calendar,
team_member_invited: UserPlus,
Expand All @@ -40,9 +44,13 @@ export function getNotificationColor(type: NotificationType): string {
event_approved: 'text-green-500',
event_rejected: 'text-red-500',
event_changes_requested: 'text-yellow-500',
event_updated: 'text-orange-500',
event_status_changed: 'text-yellow-500',
hackathon_approved: 'text-green-500',
hackathon_rejected: 'text-red-500',
hackathon_changes_requested: 'text-yellow-500',
hackathon_updated: 'text-orange-500',
hackathon_status_changed: 'text-yellow-500',
new_event_registration: 'text-blue-500',
new_hackathon_registration: 'text-blue-500',
team_member_invited: 'text-blue-500',
Expand Down
Loading
Loading