fix(portal): eslint import/order for IT page and RoleGate (CT build)
Some checks failed
CD Pipeline / Deploy to Staging (push) Failing after 3s
CI Pipeline / Lint and Type Check (push) Failing after 3s
CI Pipeline / Build (push) Has been skipped
CI Pipeline / Test Backend (push) Failing after 4s
CI Pipeline / Test Frontend (push) Failing after 4s
CI Pipeline / Security Scan (push) Failing after 36s
Deploy to Staging / Deploy to Staging (push) Failing after 8s
Portal CI / Portal Lint (push) Failing after 3s
Portal CI / Portal Type Check (push) Failing after 3s
Portal CI / Portal Test (push) Failing after 4s
Portal CI / Portal Build (push) Failing after 3s
Test Suite / frontend-tests (push) Failing after 7s
Test Suite / api-tests (push) Failing after 7s
Test Suite / blockchain-tests (push) Failing after 7s
Type Check / type-check (map[directory:. name:root]) (push) Failing after 4s
Type Check / type-check (map[directory:api name:api]) (push) Failing after 4s
Type Check / type-check (map[directory:portal name:portal]) (push) Failing after 3s
CD Pipeline / Deploy to Production (push) Has been skipped

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-09 01:25:54 -07:00
parent adb48eb76a
commit b241f52f7d
3 changed files with 94 additions and 27 deletions

View File

@@ -3,8 +3,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { RoleGate } from '@/components/auth/RoleGate';
import { IT_OPS_ALLOWED_ROLES } from '@/lib/it-ops-roles';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card';
import { IT_OPS_ALLOWED_ROLES } from '@/lib/it-ops-roles';
type DriftShape = {
collected_at?: string;

View File

@@ -0,0 +1,89 @@
'use client';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
import { type ReactNode } from 'react';
import { PortalSignInCard } from '@/components/auth/PortalSignInCard';
interface RoleGateProps {
allowedRoles: readonly string[];
callbackUrl: string;
badge: string;
title: string;
subtitle: string;
children: ReactNode;
}
function hasAllowedRole(sessionRoles: string[] | undefined, allowedRoles: readonly string[]) {
const normalizedAllowed = new Set(allowedRoles.map((role) => role.toLowerCase()));
return (sessionRoles || []).some((role) => normalizedAllowed.has(role.toLowerCase()));
}
export function RoleGate({
allowedRoles,
callbackUrl,
badge,
title,
subtitle,
children,
}: RoleGateProps) {
const { data: session, status } = useSession();
if (status === 'loading') {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4">
<div className="text-center">
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600" />
<p className="text-gray-400">Loading...</p>
</div>
</div>
);
}
if (status === 'unauthenticated') {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4 py-12">
<PortalSignInCard
badge={badge}
title={title}
subtitle={subtitle}
callbackUrl={callbackUrl}
/>
</div>
);
}
if (!hasAllowedRole(session?.roles, allowedRoles)) {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-900 px-4 py-12">
<div className="w-full max-w-lg rounded-2xl border border-gray-800 bg-gray-900/80 p-8 text-center shadow-xl shadow-black/40">
<p className="mb-1 text-sm font-medium uppercase tracking-wide text-orange-400">{badge}</p>
<h1 className="mb-3 text-2xl font-bold text-white">Access Restricted</h1>
<p className="mb-2 text-gray-400">
Your account does not currently include one of the roles required for this workspace.
</p>
<p className="mb-6 text-sm text-gray-500">
Required roles: {allowedRoles.join(', ')}
</p>
<div className="flex flex-col gap-3 pt-2 sm:flex-row sm:justify-center">
<Link
href="/"
className="inline-flex h-10 items-center justify-center rounded-md bg-gray-700 px-4 text-base font-medium text-white transition-colors hover:bg-gray-600"
>
Return Home
</Link>
<Link
href="/help/support"
className="inline-flex h-10 items-center justify-center rounded-md border border-gray-600 bg-transparent px-4 text-base font-medium text-white transition-colors hover:bg-gray-800"
>
Contact Support
</Link>
</div>
</div>
</div>
);
}
return <>{children}</>;
}

View File

@@ -1,32 +1,11 @@
'use client';
import {
LayoutDashboard,
Server,
Network,
Settings,
Activity,
Users,
CreditCard,
Shield,
Menu,
X,
} from 'lucide-react';
import { Menu, X } from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
const navigation = [
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
{ name: 'Resources', href: '/resources', icon: Server },
{ name: 'Virtual Machines', href: '/vms', icon: Server },
{ name: 'Networking', href: '/network', icon: Network },
{ name: 'Monitoring', href: '/dashboards', icon: Activity },
{ name: 'Users & Access', href: '/users', icon: Users },
{ name: 'Billing', href: '/billing', icon: CreditCard },
{ name: 'Security', href: '/security', icon: Shield },
{ name: 'Settings', href: '/settings', icon: Settings },
];
import { primaryNavigation } from '@/lib/portal-navigation';
export function MobileNavigation() {
const [isOpen, setIsOpen] = useState(false);
@@ -47,7 +26,7 @@ export function MobileNavigation() {
{isOpen && (
<div className="md:hidden fixed inset-0 z-40 bg-gray-900/95 backdrop-blur">
<nav className="flex flex-col h-full p-4 pt-20">
{navigation.map((item) => {
{primaryNavigation.map((item) => {
const Icon = item.icon;
const isActive = pathname === item.href || pathname?.startsWith(item.href + '/');
@@ -56,7 +35,7 @@ export function MobileNavigation() {
key={item.name}
href={item.href}
onClick={() => setIsOpen(false)}
className={`flex items-center gap-4 p-4 rounded-lg mb-2 transition-colors ${
className={`flex items-center gap-4 p-4 rounded-lg mb-2 no-underline transition-colors ${
isActive
? 'bg-orange-500/20 text-orange-500 border border-orange-500/20'
: 'text-gray-400 hover:bg-gray-800 hover:text-white'
@@ -73,4 +52,3 @@ export function MobileNavigation() {
</>
);
}