diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx index ea4512d..36a398b 100644 --- a/app/(dashboard)/settings/page.tsx +++ b/app/(dashboard)/settings/page.tsx @@ -26,6 +26,7 @@ interface NotificationSettings { telegramEnabled: boolean; emailEnabled: boolean; onNewVideo: boolean; + onNewVersion: boolean; onNewComment: boolean; onNewReply: boolean; timezone: string; @@ -83,6 +84,7 @@ export default function SettingsPage() { telegramEnabled: false, emailEnabled: false, onNewVideo: true, + onNewVersion: true, onNewComment: true, onNewReply: true, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', @@ -257,6 +259,14 @@ export default function SettingsPage() { label="New Video Added" description="When a new video is added to one of your projects" /> + + setSettings((s) => ({ ...s, onNewVersion: !s.onNewVersion })) + } + label="New Version Added" + description="When a new version is added to an existing video" + /> diff --git a/app/api/projects/[projectId]/videos/[videoId]/versions/route.ts b/app/api/projects/[projectId]/videos/[videoId]/versions/route.ts index e440104..4ecdaea 100644 --- a/app/api/projects/[projectId]/videos/[videoId]/versions/route.ts +++ b/app/api/projects/[projectId]/videos/[videoId]/versions/route.ts @@ -4,6 +4,7 @@ import { auth } from '@/lib/auth'; import { ProjectMemberRole, WorkspaceMemberRole } from '@prisma/client'; import { validateUrl, validateOptionalUrl } from '@/lib/validation'; import { rateLimit } from '@/lib/rate-limit'; +import { notifyProjectOwner } from '@/lib/notifications'; import { apiErrors, successResponse, withCacheControl } from '@/lib/api-response'; type RouteParams = { params: Promise<{ projectId: string; videoId: string }> }; @@ -145,6 +146,19 @@ export async function POST(request: NextRequest, { params }: RouteParams) { }); }); + // Notify project owner (fire-and-forget, skip if they added it themselves) + if (video.project.ownerId !== session.user.id) { + const baseUrl = process.env.NEXTAUTH_URL || ''; + notifyProjectOwner(video.project.ownerId, { + type: 'new_version', + projectName: video.project.name, + videoTitle: video.title, + versionLabel: version.versionLabel || `Version ${version.versionNumber}`, + addedBy: session.user.name || 'A team member', + url: `${baseUrl}/watch/${video.id}`, + }).catch((err) => console.error('Notification failed:', err)); + } + const response = successResponse(version, 201); return withCacheControl(response, 'private, no-store'); } catch (error) { diff --git a/app/api/settings/notifications/route.ts b/app/api/settings/notifications/route.ts index 6982d80..f3ea080 100644 --- a/app/api/settings/notifications/route.ts +++ b/app/api/settings/notifications/route.ts @@ -26,6 +26,7 @@ export async function GET() { telegramEnabled: false, emailEnabled: false, onNewVideo: true, + onNewVersion: true, onNewComment: true, onNewReply: true, timezone: 'UTC', @@ -57,6 +58,7 @@ export async function PUT(request: NextRequest) { telegramEnabled, emailEnabled, onNewVideo, + onNewVersion, onNewComment, onNewReply, timezone, @@ -76,6 +78,7 @@ export async function PUT(request: NextRequest) { telegramEnabled: !!telegramEnabled, emailEnabled: !!emailEnabled, onNewVideo: onNewVideo ?? true, + onNewVersion: onNewVersion ?? true, onNewComment: onNewComment ?? true, onNewReply: onNewReply ?? true, timezone: timezone || 'UTC', @@ -86,6 +89,7 @@ export async function PUT(request: NextRequest) { telegramEnabled: !!telegramEnabled, emailEnabled: !!emailEnabled, onNewVideo: onNewVideo ?? true, + onNewVersion: onNewVersion ?? true, onNewComment: onNewComment ?? true, onNewReply: onNewReply ?? true, timezone: timezone || 'UTC', diff --git a/lib/notifications.ts b/lib/notifications.ts index 3f87a0d..f4f546c 100644 --- a/lib/notifications.ts +++ b/lib/notifications.ts @@ -95,6 +95,7 @@ async function sendEmail(to: string, subject: string, html: string): Promise `), }; + case 'new_version': + return { + subject: `[OpenFrame] New version of ${event.videoTitle} in ${event.projectName}`, + html: emailTemplate(` + ${emailHeading('▶', 'New Version Added')} + + + ${emailRow('Project', escapeHtml(event.projectName), true)} + ${emailRow('Video', escapeHtml(event.videoTitle), true)} + ${emailRow('Version', escapeHtml(event.versionLabel))} + ${emailRow('Added by', escapeHtml(event.addedBy))} + ${emailRow('When', now)} +
+ ${emailButton('View Version →', event.url)} + + `), + }; case 'new_comment': return { subject: `[OpenFrame] New comment on ${event.videoTitle}`, @@ -329,6 +359,7 @@ export async function notifyProjectOwner(ownerId: string, event: NotificationEve const shouldNotify = (event.type === 'new_video' && settings.onNewVideo) || + (event.type === 'new_version' && settings.onNewVersion) || (event.type === 'new_comment' && settings.onNewComment) || (event.type === 'new_reply' && settings.onNewReply); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3f229c3..9ee5736 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -380,6 +380,7 @@ model NotificationSetting { // Event subscriptions onNewVideo Boolean @default(true) + onNewVersion Boolean @default(true) onNewComment Boolean @default(true) onNewReply Boolean @default(true)