mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
feat: Introduce guest access for video viewing, implement user notification settings via email and Telegram, and add rate limiting infrastructure.
This commit is contained in:
@@ -101,7 +101,11 @@ export default function ProjectMembersPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
setSuccess(`Invited ${data.user.name || data.user.email} as ${inviteRole.toLowerCase()}`);
|
||||
if (data.user) {
|
||||
setSuccess(`Invited ${data.user.name || data.user.email || inviteEmail} as ${inviteRole.toLowerCase()}`);
|
||||
} else {
|
||||
setSuccess(data.message || `Invitation sent to ${inviteEmail}`);
|
||||
}
|
||||
setInviteEmail('');
|
||||
fetchMembers();
|
||||
} catch {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { VideoCard } from '@/components/video-card';
|
||||
import { GuestGate } from '@/components/guest-gate';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
@@ -135,7 +136,55 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
||||
});
|
||||
|
||||
const canEdit = isOwner || project.members[0]?.role === 'ADMIN' || workspaceRole === 'OWNER' || workspaceRole === 'ADMIN';
|
||||
const isAuthenticated = !!session?.user?.id;
|
||||
|
||||
// Guest name gate for unauthenticated users on public projects
|
||||
if (!isAuthenticated && isPublic) {
|
||||
return (
|
||||
<GuestGate>
|
||||
<ProjectContent
|
||||
project={project}
|
||||
projectId={projectId}
|
||||
videos={videos}
|
||||
canEdit={false}
|
||||
isOwner={false}
|
||||
isPublic={isPublic}
|
||||
workspaceRole={null}
|
||||
/>
|
||||
</GuestGate>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProjectContent
|
||||
project={project}
|
||||
projectId={projectId}
|
||||
videos={videos}
|
||||
canEdit={canEdit}
|
||||
isOwner={isOwner}
|
||||
isPublic={isPublic}
|
||||
workspaceRole={workspaceRole}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectContent({
|
||||
project,
|
||||
projectId,
|
||||
videos,
|
||||
canEdit,
|
||||
isOwner,
|
||||
isPublic,
|
||||
workspaceRole,
|
||||
}: {
|
||||
project: { name: string; description: string | null; visibility: string; workspace: { id: string; name: string } | null; members: { role: string }[] };
|
||||
projectId: string;
|
||||
videos: { id: string; title: string; thumbnailUrl: string; currentVersion: number; commentCount: number; duration: string; lastUpdated: string }[];
|
||||
canEdit: boolean;
|
||||
isOwner: boolean;
|
||||
isPublic: boolean;
|
||||
workspaceRole: string | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="px-6 lg:px-8 py-8 w-full">
|
||||
{/* Back link */}
|
||||
|
||||
@@ -101,6 +101,7 @@ interface VideoData {
|
||||
members: { role: string }[];
|
||||
};
|
||||
versions: (Version & { comments: Comment[] })[];
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
@@ -167,6 +168,14 @@ export default function VideoPage() {
|
||||
const [isSubmittingEdit, setIsSubmittingEdit] = useState(false);
|
||||
const [deletingCommentId, setDeletingCommentId] = useState<string | null>(null);
|
||||
|
||||
// Guest name (for unauthenticated users on public projects)
|
||||
const [guestName, setGuestName] = useState('');
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('openframe_guest_name');
|
||||
if (saved) setGuestName(saved);
|
||||
}, []);
|
||||
const isGuest = video ? !video.isAuthenticated : false;
|
||||
|
||||
// New version dialog
|
||||
const [showVersionDialog, setShowVersionDialog] = useState(false);
|
||||
const [newVersionUrl, setNewVersionUrl] = useState('');
|
||||
@@ -371,6 +380,7 @@ export default function VideoPage() {
|
||||
content: voiceData ? commentText.trim() || null : commentText,
|
||||
timestamp: selectedTimestamp ?? currentTime,
|
||||
...(voiceData && { voiceUrl: voiceData.url, voiceDuration: voiceData.duration }),
|
||||
...(isGuest && guestName && { guestName }),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -395,7 +405,7 @@ export default function VideoPage() {
|
||||
} finally {
|
||||
setIsSubmittingComment(false);
|
||||
}
|
||||
}, [commentText, currentTime, selectedTimestamp, activeVersion, activeVersionId]);
|
||||
}, [commentText, currentTime, selectedTimestamp, activeVersion, activeVersionId, isGuest, guestName]);
|
||||
|
||||
// Voice recording handlers
|
||||
const startRecording = useCallback(async () => {
|
||||
@@ -631,6 +641,7 @@ export default function VideoPage() {
|
||||
timestamp: comments.find((c) => c.id === parentId)?.timestamp ?? currentTime,
|
||||
parentId,
|
||||
...(voiceData && { voiceUrl: voiceData.url, voiceDuration: voiceData.duration }),
|
||||
...(isGuest && guestName && { guestName }),
|
||||
}),
|
||||
});
|
||||
if (res.ok) {
|
||||
|
||||
Reference in New Issue
Block a user