diff --git a/src/app/dashboard/forms/page.tsx b/src/app/dashboard/forms/page.tsx index 78b02eb..2c2e181 100644 --- a/src/app/dashboard/forms/page.tsx +++ b/src/app/dashboard/forms/page.tsx @@ -1,6 +1,6 @@ import { Suspense } from 'react'; import { Metadata } from 'next'; -import FormsClient from '@/components/dashboard/forms-client'; +import FormsClient from '@/components/dashboard/forms/forms-client'; import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton'; export const metadata: Metadata = { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 595939e..27b55f3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,7 +8,7 @@ import { Header2 } from '@/components/ui/header-2'; import { AnimatedFooter } from '@/components/layout/animated-footer'; // 1. WE ADDED THE RBAC IMPORT HERE -import { RBACProvider } from '@/lib/rbac'; +import { RBACProvider, DEFAULT_RBAC_CONFIG } from '@/lib/rbac'; export const metadata: Metadata = { metadataBase: new URL('https://www.postpipe.in'), @@ -99,7 +99,7 @@ export default function RootLayout({ }>) { // 2. WE CREATED A MOCK USER TO TEST THE ROLES - const mockUser = { id: "test-user-1", role: "ADMIN" as any }; +const mockUser = { id: "test-user-1", email: "test@postpipe.in", role: "admin" }; return ( @@ -120,7 +120,7 @@ export default function RootLayout({ {/* 3. WE WRAPPED THE APP WITH RBACProvider */} - +
{children}
@@ -134,4 +134,4 @@ export default function RootLayout({ ); -} \ No newline at end of file +} diff --git a/src/components/dashboard/forms-client.tsx b/src/components/dashboard/forms/forms-client.tsx similarity index 84% rename from src/components/dashboard/forms-client.tsx rename to src/components/dashboard/forms/forms-client.tsx index ba3ca5e..e209a49 100644 --- a/src/components/dashboard/forms-client.tsx +++ b/src/components/dashboard/forms/forms-client.tsx @@ -6,7 +6,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { RainbowButton } from "@/components/ui/rainbow-button"; import { Input } from "@/components/ui/input"; -import AuthPresetGenerator from "./auth-preset-generator"; +import AuthPresetGenerator from "../auth-preset-generator"; import { Badge } from "@/components/ui/badge"; import { DropdownMenu, @@ -46,6 +46,12 @@ import IsoLevelWarp from "@/components/ui/isometric-wave-grid-background"; import { FormSearchBar } from "@/components/ui/animated-search-bar"; import { motion, AnimatePresence } from "framer-motion"; +// ── RBAC IMPORT ────────────────────────────────────────────────────────────── +import RBACPresetCard from "@/components/rbac/RBACPresetCard"; +import { DEFAULT_RBAC_CONFIG } from "@/lib/rbac/permissions"; +import type { RBACConfig } from "@/lib/rbac/types"; +// ───────────────────────────────────────────────────────────────────────────── + type Form = { id: string; name: string; @@ -73,16 +79,19 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: const [expandedFormId, setExpandedFormId] = React.useState(null); const [copiedId, setCopiedId] = React.useState(null); const [searchExpanded, setSearchExpanded] = React.useState(false); - const pendingDeletions = React.useRef>({}); + // ── RBAC state ──────────────────────────────────────────────────────────── + const [rbacConfig, setRbacConfig] = React.useState(DEFAULT_RBAC_CONFIG); + const [rbacSaved, setRbacSaved] = React.useState(false); + // ───────────────────────────────────────────────────────────────────────── + + const pendingDeletions = React.useRef>({}); const searchParams = useSearchParams(); React.useEffect(() => { const tab = searchParams.get('tab'); if (tab === 'presets') { - // Since we're using Radix/Shadcn Tabs, we might need to control the state if we want to programmatically switch. - // But if we just want to open the 'isCreatingPreset' dialog if an action is specified: const action = searchParams.get('action'); if (action === 'new-preset') { setIsCreatingPreset(true); @@ -118,10 +127,9 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: }); const [forms, setForms] = React.useState(mapForms(initialForms)); - React.useEffect(() => { + React.useEffect(() => { const mapped = mapForms(initialForms); - // Ensure pending deletions don't reappear if initialForms updates from server - setForms(mapped.filter(f => !pendingDeletions.current[f.id])); + setForms(mapped.filter(f => !pendingDeletions.current[f.id])); }, [initialForms]); const toggleStatus = async (id: string, e?: React.MouseEvent) => { @@ -154,11 +162,10 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: const deleteForm = async (id: string, e?: React.MouseEvent) => { if (e) e.stopPropagation(); - + const formToDelete = forms.find(f => f.id === id); if (!formToDelete) return; - // Optimistically remove from state setForms(prev => prev.filter(f => f.id !== id)); setExpandedFormId(null); @@ -168,13 +175,11 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: delete pendingDeletions.current[id]; router.refresh(); } catch { - // Restore if failed setForms(prev => [...prev, formToDelete]); toast({ title: "Error", description: "Failed to delete endpoint", variant: "destructive" }); } }; - // Delay the actual server action by 5 seconds const timeoutId = setTimeout(performDelete, 5000); pendingDeletions.current[id] = timeoutId; @@ -182,8 +187,8 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: title: "Endpoint Deleted", description: `"${formToDelete.name}" has been removed.`, action: ( - { if (pendingDeletions.current[id]) { clearTimeout(pendingDeletions.current[id]); @@ -243,6 +248,25 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: copyToClipboard(html, "HTML embed copied!"); }; + // ── RBAC save handler ───────────────────────────────────────────────────── + const handleRbacChange = (cfg: RBACConfig) => { + setRbacConfig(cfg); + setRbacSaved(false); + }; + + const handleRbacSave = async () => { + try { + // TODO: replace with your real server action, e.g. saveRbacConfigAction(rbacConfig) + // await saveRbacConfigAction(rbacConfig); + console.log("Saving RBAC config:", rbacConfig); + setRbacSaved(true); + toast({ title: "RBAC config saved", description: `${rbacConfig.roles.length} roles · default: ${rbacConfig.defaultRole}` }); + } catch { + toast({ title: "Failed to save RBAC config", variant: "destructive" }); + } + }; + // ───────────────────────────────────────────────────────────────────────── + const filteredForms = forms .filter(f => { if (statusFilter !== 'all' && f.status.toLowerCase() !== statusFilter) return false; @@ -263,18 +287,16 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: {/* ══ HEADER ══ */}
- {/* Animated canvas background */} - {/* Soft overlay */}
- {/* Top row: pill + button */} + {/* Top row */}
@@ -321,7 +343,7 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }:
{/* ══ TABS ══ */} - +
@@ -330,6 +352,9 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: Auth Presets + + Role Based Auth +
@@ -601,13 +626,10 @@ export default function FormsClient({ initialForms = [], initialPresets = [] }: preset.providers?.google && '"google"', preset.providers?.github && '"github"', ].filter(Boolean).join(', '); - const snip = ` -
+ const snip = `
- - + +`; + copyToClipboard(snip, "Auth snippet copied!"); + }}> + Snippet + + +
+
+ ))} +
+ )} +
+ ) : ( +
+
+
+

{editingPreset ? "Edit Configuration" : "New Auth Preset"}

+

{editingPreset ? "Update your authentication parameters." : "Configure a new drop-in authentication flow."}

+
+ +
+ { setIsCreatingPreset(false); setEditingPreset(null); router.refresh(); }} + /> +
+ )} + + + {/* ── Notun Role Based Auth Preset tab content ── */} + +
+
+
+

Role Based Access Control

+

Configure your RBAC presets and permission rules here.

+
+
+ + {/* Placeholder for the actual RBAC UI */} +
+
+ +
+
+

RBAC Settings

+

Connect your RBAC component here.

+
+
+
+
+ + + + + + ); +} \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 031fbfe..e171b4b 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -15,7 +15,7 @@ export function middleware(request: NextRequest) { const isAuthenticated = request.cookies.has(AUTH_COOKIE_NAME); // 2. Get the user's role for RBAC - const userRole = request.cookies.get('postpipe_role')?.value || 'VIEWER'; + const userRole = (request.cookies.get('postpipe_role')?.value || 'viewer').toLowerCase(); const isProtectedRoute = PROTECTED_ROUTES.some(route => pathname.startsWith(route)); @@ -42,7 +42,7 @@ export function middleware(request: NextRequest) { // 4. NEW: RBAC Authorization Check const isAdminRoute = ADMIN_ROUTES.some(route => pathname.startsWith(route)); - if (isAdminRoute && userRole !== 'ADMIN' && userRole !== 'OWNER') { + if (isAdminRoute && userRole !== 'admin' && userRole !== 'owner') { return NextResponse.redirect(new URL('/dashboard?error=unauthorized', request.url)); } @@ -51,4 +51,4 @@ export function middleware(request: NextRequest) { export const config = { matcher: ['/dashboard/:path*', '/login', '/forms', '/workflows', '/explore', '/static'], -} \ No newline at end of file +}