Remove Staff.tsx - consolidating into Admin
cgen-d4b4630792bd4ad7845f599a7eedbd34
This commit is contained in:
parent
a4d66c919d
commit
e690205c62
8 changed files with 0 additions and 1890 deletions
|
|
@ -1,263 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Layout from "@/components/Layout";
|
||||
import SEO from "@/components/SEO";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Lock, Users, FileText, Zap, Award, MessageSquare } from "lucide-react";
|
||||
|
||||
export default function Staff() {
|
||||
const { user, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && user) {
|
||||
navigate("/admin", { replace: true });
|
||||
}
|
||||
}, [user, loading, navigate]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SEO
|
||||
title="AeThex Staff"
|
||||
description="Internal platform for AeThex employees and authorized contractors"
|
||||
/>
|
||||
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-purple-950 to-slate-950 relative overflow-hidden">
|
||||
{/* Animated background */}
|
||||
<div className="absolute inset-0 opacity-30">
|
||||
<div className="absolute top-20 left-10 w-96 h-96 bg-purple-600 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
|
||||
<div className="absolute bottom-20 right-10 w-96 h-96 bg-blue-600 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
|
||||
<div className="absolute top-1/2 left-1/2 w-96 h-96 bg-indigo-600 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
{/* Hero Section */}
|
||||
<div className="container mx-auto px-4 py-20 text-center">
|
||||
<Badge className="mb-4 inline-block bg-purple-500/20 text-purple-300 border-purple-500/50">
|
||||
Internal Platform
|
||||
</Badge>
|
||||
|
||||
<h1 className="text-5xl md:text-6xl font-bold mb-6 bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">
|
||||
AeThex Staff
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-purple-200/80 max-w-2xl mx-auto mb-12">
|
||||
The internal hub for AeThex employees and authorized contractors.
|
||||
Unified access to dashboards, tools, documentation, and
|
||||
collaboration features.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={() => navigate("/admin")}
|
||||
className="bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700"
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
Admin Dashboard
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
onClick={() => navigate("/dashboard")}
|
||||
className="border-purple-500/50 text-purple-300 hover:bg-purple-500/10"
|
||||
>
|
||||
Back to AeThex
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Grid */}
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
<h2 className="text-3xl font-bold text-center mb-12 text-purple-100">
|
||||
Staff Tools & Features
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{/* Dashboard */}
|
||||
<Card className="border-purple-500/30 bg-purple-950/30 backdrop-blur hover:border-purple-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-purple-500/20">
|
||||
<Zap className="h-5 w-5 text-purple-400" />
|
||||
</div>
|
||||
<CardTitle className="text-purple-100">Dashboard</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-purple-200/70">
|
||||
Real-time operations metrics, service status, and quick
|
||||
access to common tools.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Directory */}
|
||||
<Card className="border-blue-500/30 bg-blue-950/30 backdrop-blur hover:border-blue-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-blue-500/20">
|
||||
<Users className="h-5 w-5 text-blue-400" />
|
||||
</div>
|
||||
<CardTitle className="text-blue-100">Directory</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-blue-200/70">
|
||||
Browse team members, view profiles, and find contact
|
||||
information.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Admin Tools */}
|
||||
<Card className="border-indigo-500/30 bg-indigo-950/30 backdrop-blur hover:border-indigo-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-indigo-500/20">
|
||||
<Lock className="h-5 w-5 text-indigo-400" />
|
||||
</div>
|
||||
<CardTitle className="text-indigo-100">
|
||||
Admin Tools
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-indigo-200/70">
|
||||
Manage users, roles, permissions, and platform
|
||||
configuration.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Docs */}
|
||||
<Card className="border-cyan-500/30 bg-cyan-950/30 backdrop-blur hover:border-cyan-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-cyan-500/20">
|
||||
<FileText className="h-5 w-5 text-cyan-400" />
|
||||
</div>
|
||||
<CardTitle className="text-cyan-100">Docs & API</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-cyan-200/70">
|
||||
Internal documentation, API keys, credentials, and setup
|
||||
guides.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Achievements */}
|
||||
<Card className="border-amber-500/30 bg-amber-950/30 backdrop-blur hover:border-amber-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-amber-500/20">
|
||||
<Award className="h-5 w-5 text-amber-400" />
|
||||
</div>
|
||||
<CardTitle className="text-amber-100">
|
||||
Achievements
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-amber-200/70">
|
||||
Track team accomplishments, milestones, and performance
|
||||
metrics.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Collaboration */}
|
||||
<Card className="border-rose-500/30 bg-rose-950/30 backdrop-blur hover:border-rose-400/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="p-2 rounded bg-rose-500/20">
|
||||
<MessageSquare className="h-5 w-5 text-rose-400" />
|
||||
</div>
|
||||
<CardTitle className="text-rose-100">
|
||||
Collaboration
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-rose-200/70">
|
||||
Internal chat, team discussions, and project coordination.
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info Section */}
|
||||
<div className="container mx-auto px-4 py-20 border-t border-purple-500/20">
|
||||
<div className="grid md:grid-cols-2 gap-12">
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold mb-4 text-purple-100">
|
||||
Who Can Access?
|
||||
</h3>
|
||||
<ul className="space-y-3 text-purple-200/80">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">✓</span>
|
||||
<span>AeThex employees (@aethex.dev)</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">✓</span>
|
||||
<span>Authorized contractors (invited)</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">✓</span>
|
||||
<span>Partners with special access</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold mb-4 text-purple-100">
|
||||
Getting Started
|
||||
</h3>
|
||||
<ol className="space-y-3 text-purple-200/80">
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">1.</span>
|
||||
<span>Click "Staff Login" to sign in with Google</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">2.</span>
|
||||
<span>Use your @aethex.dev email address</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="text-purple-400">3.</span>
|
||||
<span>Access your personalized dashboard</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer CTA */}
|
||||
<div className="container mx-auto px-4 py-12 text-center border-t border-purple-500/20">
|
||||
<p className="text-purple-300 mb-6">
|
||||
Not a staff member? Visit the public platform.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate("/")}
|
||||
className="border-purple-500/50 text-purple-300 hover:bg-purple-500/10"
|
||||
>
|
||||
Back to Main Site
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,411 +0,0 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { aethexToast } from "@/lib/aethex-toast";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
function useHasStaffAccess(roles: string[]) {
|
||||
return useMemo(
|
||||
() =>
|
||||
roles.some((r) =>
|
||||
["owner", "admin", "founder", "staff", "employee"].includes(
|
||||
r.toLowerCase(),
|
||||
),
|
||||
),
|
||||
[roles],
|
||||
);
|
||||
}
|
||||
|
||||
export default function StaffDashboard() {
|
||||
const { user, roles, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const hasAccess = useHasStaffAccess(roles);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
if (!user) {
|
||||
aethexToast.info({
|
||||
title: "Sign in required",
|
||||
description: "Staff area requires authentication",
|
||||
});
|
||||
navigate("/admin");
|
||||
return;
|
||||
}
|
||||
if (!hasAccess) {
|
||||
aethexToast.error({
|
||||
title: "Access denied",
|
||||
description: "You don't have staff permissions",
|
||||
});
|
||||
navigate("/dashboard");
|
||||
}
|
||||
}, [user, roles, hasAccess, loading, navigate]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [openReports, setOpenReports] = useState<any[]>([]);
|
||||
const [mentorshipAll, setMentorshipAll] = useState<any[]>([]);
|
||||
const [loadingData, setLoadingData] = useState(false);
|
||||
const [searchQ, setSearchQ] = useState("");
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
|
||||
const refresh = async () => {
|
||||
setLoadingData(true);
|
||||
try {
|
||||
const [r1, r2] = await Promise.all([
|
||||
fetch("/api/moderation/reports?status=open&limit=100"),
|
||||
fetch("/api/mentorship/requests/all?limit=50&status=pending"),
|
||||
]);
|
||||
const reports = r1.ok ? await r1.json() : [];
|
||||
const m = r2.ok ? await r2.json() : [];
|
||||
setOpenReports(Array.isArray(reports) ? reports : []);
|
||||
setMentorshipAll(Array.isArray(m) ? m : []);
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
} finally {
|
||||
setLoadingData(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user && hasAccess) refresh();
|
||||
}, [user, hasAccess]);
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (searchQ.trim()) params.set("q", searchQ.trim());
|
||||
params.set("limit", "25");
|
||||
const resp = await fetch(`/api/staff/users?${params.toString()}`);
|
||||
const data = resp.ok ? await resp.json() : [];
|
||||
setUsers(Array.isArray(data) ? data : []);
|
||||
} catch {
|
||||
setUsers([]);
|
||||
}
|
||||
};
|
||||
|
||||
const updateReportStatus = async (
|
||||
id: string,
|
||||
status: "resolved" | "ignored" | "open",
|
||||
) => {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/moderation/reports/${encodeURIComponent(id)}/status`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ status }),
|
||||
},
|
||||
);
|
||||
if (resp.ok) {
|
||||
aethexToast.success({
|
||||
title: "Updated",
|
||||
description: `Report marked ${status}`,
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-10">
|
||||
<div className="mb-6">
|
||||
<Badge variant="outline" className="mb-2">
|
||||
Internal
|
||||
</Badge>
|
||||
<h1 className="text-3xl font-bold">Operations Command</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Staff dashboards, moderation, and internal tools.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="moderation">Moderation</TabsTrigger>
|
||||
<TabsTrigger value="mentorship">Mentorship</TabsTrigger>
|
||||
<TabsTrigger value="users">Users</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="mt-6">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Community Health</CardTitle>
|
||||
<CardDescription>
|
||||
Quick pulse across posts and reports
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Open reports
|
||||
</div>
|
||||
<div className="text-xl font-semibold">
|
||||
{openReports.length}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Mentorship requests
|
||||
</div>
|
||||
<div className="text-xl font-semibold">
|
||||
{mentorshipAll.length}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Service Status</CardTitle>
|
||||
<CardDescription>APIs and queues</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Admin API
|
||||
</div>
|
||||
<Badge className="bg-emerald-600">OK</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Notifications
|
||||
</div>
|
||||
<Badge className="bg-emerald-600">OK</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Staff Resources</CardTitle>
|
||||
<CardDescription>Knowledge & HR tools</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/knowledge-base">Knowledge Base</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/team-handbook">Team Handbook</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/announcements">Announcements</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/learning-portal">Learning Portal</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Operations</CardTitle>
|
||||
<CardDescription>Tracking and management</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/project-tracking">Project Tracking</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/announcements">Announcements</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Employee Services</CardTitle>
|
||||
<CardDescription>HR and financial tools</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/performance-reviews">
|
||||
Performance Reviews
|
||||
</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/expenses">Expense Reports</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<a href="/admin/staff/marketplace">Internal Marketplace</a>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="moderation" className="mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Moderation Queue</CardTitle>
|
||||
<CardDescription>Flagged content and actions</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loadingData && (
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
)}
|
||||
{!loadingData && openReports.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No items in queue.
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-3">
|
||||
{openReports.map((r) => (
|
||||
<div
|
||||
key={r.id}
|
||||
className="rounded border border-border/50 p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium">{r.reason}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{r.details}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => updateReportStatus(r.id, "ignored")}
|
||||
>
|
||||
Ignore
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => updateReportStatus(r.id, "resolved")}
|
||||
>
|
||||
Resolve
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="mentorship" className="mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Mentorship Requests</CardTitle>
|
||||
<CardDescription>
|
||||
Review recent mentor/mentee activity
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loadingData && (
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
)}
|
||||
{!loadingData && mentorshipAll.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No requests to review.
|
||||
</p>
|
||||
)}
|
||||
<div className="space-y-3">
|
||||
{mentorshipAll.map((req) => (
|
||||
<div
|
||||
key={req.id}
|
||||
className="rounded border border-border/50 p-3"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
{req.mentee?.full_name || req.mentee?.username} →{" "}
|
||||
{req.mentor?.full_name || req.mentor?.username}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{req.message || "No message"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs capitalize"
|
||||
>
|
||||
{req.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="flex gap-2">
|
||||
<Button asChild>
|
||||
<a href="/community/mentorship">Open requests</a>
|
||||
</Button>
|
||||
<Button asChild variant="outline">
|
||||
<a href="/community/mentorship/apply">Mentor directory</a>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="users" className="mt-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Users</CardTitle>
|
||||
<CardDescription>
|
||||
Search, roles, and quick actions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
className="w-full rounded border border-border/50 bg-background px-3 py-2 text-sm"
|
||||
placeholder="Search by name or username"
|
||||
value={searchQ}
|
||||
onChange={(e) => setSearchQ(e.target.value)}
|
||||
/>
|
||||
<Button onClick={loadUsers} variant="outline">
|
||||
Search
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded border border-border/50">
|
||||
{users.length === 0 ? (
|
||||
<p className="p-3 text-sm text-muted-foreground">
|
||||
No users found.
|
||||
</p>
|
||||
) : (
|
||||
<div className="divide-y divide-border/50">
|
||||
{users.map((u) => (
|
||||
<div
|
||||
key={u.id}
|
||||
className="flex items-center justify-between p-3"
|
||||
>
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
{u.full_name || u.username || u.id}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{u.username}
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="capitalize">
|
||||
{u.user_type || "unknown"}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useAethexToast } from "@/hooks/use-aethex-toast";
|
||||
import Layout from "@/components/Layout";
|
||||
import SEO from "@/components/SEO";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import LoadingScreen from "@/components/LoadingScreen";
|
||||
import { LogIn, Lock, Users } from "lucide-react";
|
||||
|
||||
const GoogleIcon = () => (
|
||||
<svg
|
||||
className="h-4 w-4"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function StaffLogin() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errorFromUrl, setErrorFromUrl] = useState<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { signInWithOAuth, user, loading, profileComplete } = useAuth();
|
||||
const { error: toastError, info: toastInfo } = useAethexToast();
|
||||
|
||||
// Check for error messages from URL (e.g., from OAuth callbacks)
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const errorType = params.get("error");
|
||||
const errorMessage = params.get("message");
|
||||
|
||||
if (errorType && errorMessage) {
|
||||
setErrorFromUrl(decodeURIComponent(errorMessage));
|
||||
if (errorType === "domain_not_allowed") {
|
||||
toastError({
|
||||
title: "Access Denied",
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [location.search, toastError]);
|
||||
|
||||
// Redirect if already authenticated (with @aethex.dev email validation)
|
||||
useEffect(() => {
|
||||
if (!loading && user) {
|
||||
const userEmail = user.email || "";
|
||||
const isAethexDev = userEmail.endsWith("@aethex.dev");
|
||||
|
||||
if (!isAethexDev) {
|
||||
// Email is not @aethex.dev - show error
|
||||
setErrorFromUrl(
|
||||
"Only @aethex.dev email addresses can access the Staff Portal. If you're an authorized contractor, please use your assigned contractor email.",
|
||||
);
|
||||
toastError({
|
||||
title: "Access Denied",
|
||||
description: "This email domain is not authorized for staff access.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Valid staff email - redirect to admin dashboard
|
||||
const params = new URLSearchParams(location.search);
|
||||
const next = params.get("next");
|
||||
const safeNext = next && next.startsWith("/admin") ? next : null;
|
||||
navigate(safeNext || "/admin", { replace: true });
|
||||
}
|
||||
}, [user, loading, navigate, location.search, toastError]);
|
||||
|
||||
const handleGoogleSignIn = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Pass the admin dashboard as the intended destination after OAuth completes
|
||||
await signInWithOAuth("google", "/admin");
|
||||
} catch (error: any) {
|
||||
console.error("Google sign-in error:", error);
|
||||
toastError({
|
||||
title: "Sign-in failed",
|
||||
description:
|
||||
error?.message || "Failed to sign in with Google. Please try again.",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<SEO title="Staff Login" description="AeThex staff and employees only" />
|
||||
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-20 left-10 w-72 h-72 bg-purple-500 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
|
||||
<div className="absolute bottom-20 right-10 w-72 h-72 bg-blue-500 rounded-full mix-blend-multiply filter blur-3xl animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md">
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardHeader className="space-y-2 text-center">
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="p-3 rounded-lg bg-purple-500/20 border border-purple-500/30">
|
||||
<Lock className="h-6 w-6 text-purple-400" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle className="text-2xl font-bold">Staff Portal</CardTitle>
|
||||
<CardDescription>
|
||||
AeThex employees and authorized contractors only
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
{errorFromUrl && (
|
||||
<Alert className="border-red-400/30 bg-red-500/10 text-foreground">
|
||||
<AlertTitle>Access Denied</AlertTitle>
|
||||
<AlertDescription>{errorFromUrl}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm text-slate-400 text-center">
|
||||
Sign in with your @aethex.dev Google Workspace account
|
||||
</p>
|
||||
|
||||
<Button
|
||||
onClick={handleGoogleSignIn}
|
||||
disabled={isLoading}
|
||||
size="lg"
|
||||
className="w-full bg-white hover:bg-slate-100 text-slate-900 font-semibold"
|
||||
>
|
||||
<GoogleIcon />
|
||||
<span>
|
||||
{isLoading ? "Signing in..." : "Sign in with Google"}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-slate-700" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-slate-900 text-slate-400">
|
||||
For team members only
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 text-sm text-slate-400">
|
||||
<div className="flex gap-2">
|
||||
<Users className="h-4 w-4 text-purple-400 flex-shrink-0 mt-0.5" />
|
||||
<span>Full access to internal tools and dashboards</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Lock className="h-4 w-4 text-purple-400 flex-shrink-0 mt-0.5" />
|
||||
<span>Secured with Google Workspace authentication</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-slate-700">
|
||||
<p className="text-xs text-slate-500 text-center">
|
||||
Public login available at{" "}
|
||||
<a
|
||||
href="/login"
|
||||
className="text-purple-400 hover:text-purple-300"
|
||||
>
|
||||
aethex.dev/login
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Trophy, Zap, Users, Target } from "lucide-react";
|
||||
|
||||
interface Achievement {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
earned: boolean;
|
||||
earnedDate?: string;
|
||||
progress?: number;
|
||||
}
|
||||
|
||||
export default function StaffAchievements() {
|
||||
const { user, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [achievements, setAchievements] = useState<Achievement[]>([
|
||||
{
|
||||
id: "1",
|
||||
title: "Team Contributor",
|
||||
description: "Contribute to 5 team projects",
|
||||
icon: "Users",
|
||||
earned: true,
|
||||
earnedDate: "2025-01-15",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Code Master",
|
||||
description: "100+ commits to main repository",
|
||||
icon: "Zap",
|
||||
earned: false,
|
||||
progress: 67,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Documentation Champion",
|
||||
description: "Complete internal documentation setup",
|
||||
icon: "Target",
|
||||
earned: true,
|
||||
earnedDate: "2025-01-10",
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Mentor",
|
||||
description: "Mentor 3 team members",
|
||||
icon: "Trophy",
|
||||
earned: false,
|
||||
progress: 33,
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
navigate("/admin");
|
||||
}
|
||||
}, [user, loading, navigate]);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container py-20">Loading...</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">Achievements</h1>
|
||||
<p className="text-slate-400">
|
||||
Track team accomplishments and milestones
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-4 gap-6 mb-12">
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="text-sm text-slate-400 mb-2">
|
||||
Total Achievements
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-white">4</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="text-sm text-slate-400 mb-2">Earned</div>
|
||||
<div className="text-3xl font-bold text-green-400">2</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="text-sm text-slate-400 mb-2">In Progress</div>
|
||||
<div className="text-3xl font-bold text-blue-400">2</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="text-sm text-slate-400 mb-2">
|
||||
Completion Rate
|
||||
</div>
|
||||
<div className="text-3xl font-bold text-purple-400">50%</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Achievements Grid */}
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{achievements.map((achievement) => (
|
||||
<Card
|
||||
key={achievement.id}
|
||||
className={`border-slate-700/50 backdrop-blur transition-colors ${
|
||||
achievement.earned
|
||||
? "bg-green-500/5 border-green-500/30"
|
||||
: "bg-slate-900/50 border-slate-700/50"
|
||||
} hover:border-slate-600/50`}
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div
|
||||
className={`p-3 rounded-lg ${
|
||||
achievement.earned
|
||||
? "bg-green-500/20"
|
||||
: "bg-slate-700/50"
|
||||
}`}
|
||||
>
|
||||
<Trophy
|
||||
className={`h-6 w-6 ${
|
||||
achievement.earned
|
||||
? "text-green-400"
|
||||
: "text-slate-400"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle
|
||||
className={
|
||||
achievement.earned ? "text-green-300" : "text-white"
|
||||
}
|
||||
>
|
||||
{achievement.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400 mt-1">
|
||||
{achievement.description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
{achievement.earned && (
|
||||
<Badge className="bg-green-500/30 text-green-300 border-green-500/50">
|
||||
Earned
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{!achievement.earned &&
|
||||
achievement.progress !== undefined && (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<span className="text-xs text-slate-400">
|
||||
Progress
|
||||
</span>
|
||||
<span className="text-xs text-slate-300 font-medium">
|
||||
{achievement.progress}%
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={achievement.progress}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{achievement.earnedDate && (
|
||||
<p className="text-xs text-slate-500">
|
||||
Earned on{" "}
|
||||
{new Date(achievement.earnedDate).toLocaleDateString()}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Users,
|
||||
Shield,
|
||||
Settings,
|
||||
GitBranch,
|
||||
Eye,
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
|
||||
export default function StaffAdmin() {
|
||||
const { user, roles, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
navigate("/admin");
|
||||
return;
|
||||
}
|
||||
const isAdmin = roles?.some((r) =>
|
||||
["owner", "admin", "founder"].includes(r.toLowerCase()),
|
||||
);
|
||||
if (!isAdmin) {
|
||||
navigate("/admin");
|
||||
}
|
||||
}, [user, roles, loading, navigate]);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container py-20">Loading...</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">Admin Tools</h1>
|
||||
<p className="text-slate-400">
|
||||
Manage users, roles, and platform configuration
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{/* User Management */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Users className="h-5 w-5 text-blue-400" />
|
||||
Users
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Manage team members and roles
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-blue-600 hover:bg-blue-700">
|
||||
Manage Users
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Permissions */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Shield className="h-5 w-5 text-purple-400" />
|
||||
Permissions
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Configure role-based access
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-purple-600 hover:bg-purple-700">
|
||||
Manage Roles
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Settings */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Settings className="h-5 w-5 text-indigo-400" />
|
||||
Settings
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Platform configuration
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-indigo-600 hover:bg-indigo-700">
|
||||
Edit Settings
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* API Keys */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<GitBranch className="h-5 w-5 text-green-400" />
|
||||
API Keys
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Manage authentication tokens
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700">
|
||||
View API Keys
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Audit Log */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Eye className="h-5 w-5 text-yellow-400" />
|
||||
Audit Log
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Platform activity history
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-yellow-600 hover:bg-yellow-700">
|
||||
View Logs
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Maintenance */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<RefreshCw className="h-5 w-5 text-red-400" />
|
||||
Maintenance
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
System operations
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button className="w-full bg-red-600 hover:bg-red-700">
|
||||
System Maintenance
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { MessageSquare, Users, Hash, Lock, Plus } from "lucide-react";
|
||||
|
||||
const channels = [
|
||||
{
|
||||
name: "announcements",
|
||||
description: "Company announcements and updates",
|
||||
type: "public",
|
||||
members: 45,
|
||||
},
|
||||
{
|
||||
name: "engineering",
|
||||
description: "Engineering team discussions",
|
||||
type: "private",
|
||||
members: 12,
|
||||
},
|
||||
{
|
||||
name: "product",
|
||||
description: "Product and feature discussions",
|
||||
type: "public",
|
||||
members: 28,
|
||||
},
|
||||
{
|
||||
name: "design",
|
||||
description: "Design and UX team",
|
||||
type: "private",
|
||||
members: 8,
|
||||
},
|
||||
{
|
||||
name: "random",
|
||||
description: "Off-topic and casual chat",
|
||||
type: "public",
|
||||
members: 42,
|
||||
},
|
||||
];
|
||||
|
||||
const directMessages = [
|
||||
{ name: "Sarah Johnson", role: "CEO", status: "online" },
|
||||
{ name: "Mike Chen", role: "CTO", status: "online" },
|
||||
{ name: "Emma Davis", role: "Product Lead", status: "away" },
|
||||
{ name: "Alex Kim", role: "Designer", status: "offline" },
|
||||
];
|
||||
|
||||
export default function StaffChat() {
|
||||
const { user, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
navigate("/admin");
|
||||
}
|
||||
}, [user, loading, navigate]);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container py-20">Loading...</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">Team Chat</h1>
|
||||
<p className="text-slate-400">
|
||||
Internal collaboration and team discussions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{/* Sidebar */}
|
||||
<div className="space-y-6">
|
||||
{/* Channels */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
<Hash className="h-5 w-5 text-slate-400" />
|
||||
Channels
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-slate-400 hover:text-white p-0"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{channels.map((channel) => (
|
||||
<Button
|
||||
key={channel.name}
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-slate-300 hover:text-white hover:bg-slate-700/50"
|
||||
>
|
||||
<Hash className="h-4 w-4 mr-2" />
|
||||
{channel.name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DMs */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-white flex items-center gap-2">
|
||||
<MessageSquare className="h-5 w-5 text-slate-400" />
|
||||
Direct Messages
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-slate-400 hover:text-white p-0"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{directMessages.map((dm) => (
|
||||
<Button
|
||||
key={dm.name}
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-slate-300 hover:text-white hover:bg-slate-700/50"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="w-2 h-2 rounded-full bg-slate-400" />
|
||||
<span className="truncate">{dm.name}</span>
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Chat Area */}
|
||||
<div className="md:col-span-2 space-y-6">
|
||||
{/* Channel Info */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2 text-white">
|
||||
<Hash className="h-5 w-5" />
|
||||
announcements
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Company announcements and updates
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/50">
|
||||
45 members
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{/* Messages Area */}
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur h-96 flex flex-col">
|
||||
<CardContent className="flex-1 overflow-y-auto p-6 space-y-4">
|
||||
<div className="flex gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-blue-500/20 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-white font-medium">
|
||||
Sarah Johnson
|
||||
</p>
|
||||
<p className="text-slate-300 text-sm mt-1">
|
||||
Welcome to the internal team chat! This is where we
|
||||
collaborate and share updates.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 mt-2">10:30 AM</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-purple-500/20 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-sm text-white font-medium">
|
||||
Mike Chen
|
||||
</p>
|
||||
<p className="text-slate-300 text-sm mt-1">
|
||||
Great! Looking forward to building amazing things
|
||||
together.
|
||||
</p>
|
||||
<p className="text-xs text-slate-500 mt-2">10:35 AM</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<div className="border-t border-slate-700/50 p-4">
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Type a message..."
|
||||
className="flex-1 px-4 py-2 rounded bg-slate-800 border border-slate-700 text-white placeholder-slate-500 focus:outline-none focus:border-purple-500"
|
||||
/>
|
||||
<Button className="bg-purple-600 hover:bg-purple-700">
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Info */}
|
||||
<Card className="border-blue-500/30 bg-blue-500/10 backdrop-blur">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-blue-300 flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
Team Chat Coming Soon
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-blue-200/80 space-y-2">
|
||||
<p>• Full-featured internal messaging platform</p>
|
||||
<p>• Channels for teams and projects</p>
|
||||
<p>• Direct messages and group chats</p>
|
||||
<p>• File sharing and integrations</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { aethexToast } from "@/lib/aethex-toast";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, Mail, Phone, Briefcase } from "lucide-react";
|
||||
|
||||
interface StaffMember {
|
||||
id: string;
|
||||
full_name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
department?: string;
|
||||
position?: string;
|
||||
avatar_url?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
export default function StaffDirectory() {
|
||||
const { user, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [staffMembers, setStaffMembers] = useState<StaffMember[]>([]);
|
||||
const [filteredMembers, setFilteredMembers] = useState<StaffMember[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
navigate("/admin");
|
||||
}
|
||||
}, [user, loading, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMembers = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/staff/members");
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setStaffMembers(Array.isArray(data) ? data : []);
|
||||
setFilteredMembers(Array.isArray(data) ? data : []);
|
||||
} else {
|
||||
aethexToast.error({
|
||||
title: "Failed to load directory",
|
||||
description: "Could not fetch staff members",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching staff members:", error);
|
||||
aethexToast.error({
|
||||
title: "Error",
|
||||
description: "Failed to load staff directory",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (user) fetchMembers();
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery.trim() === "") {
|
||||
setFilteredMembers(staffMembers);
|
||||
} else {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const filtered = staffMembers.filter(
|
||||
(member) =>
|
||||
member.full_name.toLowerCase().includes(query) ||
|
||||
member.email.toLowerCase().includes(query) ||
|
||||
member.department?.toLowerCase().includes(query),
|
||||
);
|
||||
setFilteredMembers(filtered);
|
||||
}
|
||||
}, [searchQuery, staffMembers]);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container py-20">Loading...</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">
|
||||
Team Directory
|
||||
</h1>
|
||||
<p className="text-slate-400">
|
||||
Find and connect with AeThex team members
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<Card className="mb-8 border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
|
||||
<Input
|
||||
placeholder="Search by name, email, or department..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-500"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Results */}
|
||||
{isLoading ? (
|
||||
<div className="text-center py-12 text-slate-400">
|
||||
Loading team members...
|
||||
</div>
|
||||
) : filteredMembers.length === 0 ? (
|
||||
<div className="text-center py-12 text-slate-400">
|
||||
No staff members found matching your search
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredMembers.map((member) => (
|
||||
<Card
|
||||
key={member.id}
|
||||
className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors"
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-white">
|
||||
{member.full_name}
|
||||
</CardTitle>
|
||||
<p className="text-sm text-slate-400 mt-1">
|
||||
{member.position || "Team Member"}
|
||||
</p>
|
||||
</div>
|
||||
{member.role && (
|
||||
<Badge className="bg-purple-500/30 text-purple-300 border-purple-500/50 capitalize">
|
||||
{member.role}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||
<Mail className="h-4 w-4 text-slate-500" />
|
||||
<a
|
||||
href={`mailto:${member.email}`}
|
||||
className="hover:text-purple-400"
|
||||
>
|
||||
{member.email}
|
||||
</a>
|
||||
</div>
|
||||
{member.phone && (
|
||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||
<Phone className="h-4 w-4 text-slate-500" />
|
||||
<a
|
||||
href={`tel:${member.phone}`}
|
||||
className="hover:text-purple-400"
|
||||
>
|
||||
{member.phone}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{member.department && (
|
||||
<div className="flex items-center gap-2 text-slate-400 text-sm">
|
||||
<Briefcase className="h-4 w-4 text-slate-500" />
|
||||
<span>{member.department}</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="mt-12 pt-8 border-t border-slate-700/50">
|
||||
<p className="text-center text-slate-400">
|
||||
Showing {filteredMembers.length} of {staffMembers.length} team
|
||||
members
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import Layout from "@/components/Layout";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
} from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { FileText, Key, Database, Code2, ExternalLink } from "lucide-react";
|
||||
|
||||
export default function StaffDocs() {
|
||||
const { user, loading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !user) {
|
||||
navigate("/admin");
|
||||
}
|
||||
}, [user, loading, navigate]);
|
||||
|
||||
if (loading)
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container py-20">Loading...</div>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
const docs = [
|
||||
{
|
||||
icon: FileText,
|
||||
title: "Getting Started",
|
||||
description: "Onboarding guide and platform overview",
|
||||
link: "#",
|
||||
},
|
||||
{
|
||||
icon: Code2,
|
||||
title: "API Reference",
|
||||
description: "Complete API documentation and endpoints",
|
||||
link: "#",
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
title: "Database Schema",
|
||||
description: "Database structure and relationships",
|
||||
link: "#",
|
||||
},
|
||||
{
|
||||
icon: Key,
|
||||
title: "Authentication",
|
||||
description: "OAuth, tokens, and security guidelines",
|
||||
link: "#",
|
||||
},
|
||||
];
|
||||
|
||||
const apiKeys = [
|
||||
{ name: "Public API Key", key: "pk_aethex_...", status: "active" },
|
||||
{ name: "Secret API Key", key: "sk_aethex_...", status: "active" },
|
||||
{ name: "Webhook Secret", key: "whk_aethex_...", status: "active" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">
|
||||
Documentation & API
|
||||
</h1>
|
||||
<p className="text-slate-400">
|
||||
Internal docs, API keys, and credentials
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Documentation */}
|
||||
<div className="mb-12">
|
||||
<h2 className="text-2xl font-bold text-white mb-6">
|
||||
Documentation
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{docs.map((doc, idx) => {
|
||||
const IconComponent = doc.icon;
|
||||
return (
|
||||
<Card
|
||||
key={idx}
|
||||
className="border-slate-700/50 bg-slate-900/50 backdrop-blur hover:border-slate-600/50 transition-colors cursor-pointer"
|
||||
>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="p-2 rounded bg-blue-500/20">
|
||||
<IconComponent className="h-5 w-5 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-white">
|
||||
{doc.title}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
{doc.description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-blue-400 hover:text-blue-300 p-0"
|
||||
>
|
||||
Read Docs <ExternalLink className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* API Keys */}
|
||||
<div className="mb-12">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold text-white">API Keys</h2>
|
||||
<Badge className="bg-yellow-500/20 text-yellow-300 border-yellow-500/50">
|
||||
Keep secure
|
||||
</Badge>
|
||||
</div>
|
||||
<Card className="border-slate-700/50 bg-slate-900/50 backdrop-blur">
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
{apiKeys.map((key, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="flex items-center justify-between p-4 rounded border border-slate-700/50 bg-slate-800/30"
|
||||
>
|
||||
<div>
|
||||
<p className="text-white font-medium">{key.name}</p>
|
||||
<p className="text-slate-400 text-sm font-mono">
|
||||
{key.key}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/50 capitalize">
|
||||
{key.status}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-blue-400 hover:text-blue-300"
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button className="w-full mt-6 bg-blue-600 hover:bg-blue-700">
|
||||
Generate New Key
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Security Notice */}
|
||||
<Card className="border-red-500/30 bg-red-500/10 backdrop-blur">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-red-300">🔐 Security Notice</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-red-200/80 space-y-2">
|
||||
<p>• Never share API keys in public channels or repositories</p>
|
||||
<p>• Rotate keys regularly for security</p>
|
||||
<p>• Use secrets management for production deployments</p>
|
||||
<p>• Report compromised keys immediately</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue