aethex-forge/client/components/settings/RealmSwitcher.tsx
sirpiglr bce04f18cd Update realm options to align with app-wide taxonomy
Aligns RealmSwitcher realm IDs with the ARMS taxonomy used throughout the application, ensuring proper persistence and pre-selection of saved realms. Adds JWT authentication to the profile update endpoint and updates the dashboard to send authentication tokens with API requests.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 83f304a8-9190-4f3a-a7b9-ba30cbf05d91
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/xCMzR8i
Replit-Helium-Checkpoint-Created: true
2025-12-04 09:53:13 +00:00

352 lines
11 KiB
TypeScript

import { Link } from "react-router-dom";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import {
Code,
Rocket,
Users,
Trophy,
Database,
Sparkles,
Shield,
Compass,
ArrowRight,
Check,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useAuth } from "@/contexts/AuthContext";
import { useMemo, memo, type ComponentType } from "react";
export type RealmKey =
| "labs"
| "gameforge"
| "corp"
| "foundation"
| "devlink"
| "nexus"
| "staff";
export interface RealmOption {
id: RealmKey;
name: string;
title: string;
description: string;
icon: ComponentType<{ className?: string }>;
gradient: string;
route: string;
routeLabel: string;
highlights: string[];
staffOnly?: boolean;
}
export const REALM_OPTIONS: RealmOption[] = [
{
id: "labs",
name: "Research & Development",
title: "Labs",
description:
"Explore cutting-edge research, experimental features, and contribute to the future of AeThex technology.",
icon: Code,
gradient: "from-amber-400 to-amber-600",
route: "/dashboard/labs",
routeLabel: "Labs Dashboard",
highlights: [
"Access experimental features",
"Contribute to R&D projects",
"Technical deep-dives",
],
},
{
id: "gameforge",
name: "Game Development",
title: "GameForge",
description:
"Build immersive experiences, collaborate with other creators, and unlock our full suite of game development tools.",
icon: Rocket,
gradient: "from-green-400 to-green-600",
route: "/gameforge",
routeLabel: "GameForge",
highlights: [
"Advanced tooling and workflows",
"Collaborative project spaces",
"Mentors and technical reviews",
],
},
{
id: "corp",
name: "Business & Consulting",
title: "Corp",
description:
"Engage AeThex teams for bespoke solutions, product consulting, and strategic execution across every milestone.",
icon: Users,
gradient: "from-blue-400 to-blue-600",
route: "/hub/client",
routeLabel: "Client Hub",
highlights: [
"Project leadership & delivery",
"End-to-end service orchestration",
"Outcome-driven partnership",
],
},
{
id: "foundation",
name: "Education & Mentorship",
title: "Foundation",
description:
"Access courses, mentorship programs, and educational resources to grow your skills and advance your career.",
icon: Trophy,
gradient: "from-red-400 to-red-600",
route: "/foundation",
routeLabel: "Foundation",
highlights: [
"Structured learning paths",
"Expert mentorship",
"Achievement-based progression",
],
},
{
id: "devlink",
name: "Developer Network",
title: "Dev-Link",
description:
"Connect with developers, share knowledge, and collaborate on open-source projects across the AeThex ecosystem.",
icon: Database,
gradient: "from-cyan-400 to-cyan-600",
route: "/dashboard/dev-link",
routeLabel: "Dev-Link",
highlights: [
"Developer networking",
"Open-source collaboration",
"API access and integrations",
],
},
{
id: "nexus",
name: "Talent Marketplace",
title: "Nexus",
description:
"Find opportunities, showcase your skills, and connect with projects seeking talented creators and developers.",
icon: Sparkles,
gradient: "from-purple-400 to-fuchsia-600",
route: "/nexus",
routeLabel: "Nexus Marketplace",
highlights: [
"Job and contract opportunities",
"Portfolio showcase",
"Talent matching",
],
},
{
id: "staff",
name: "Operations Command",
title: "Staff",
description:
"Admin realm for site staff and employees: operations dashboards, moderation, and admin tooling.",
icon: Shield,
gradient: "from-violet-500 to-purple-700",
route: "/staff/dashboard",
routeLabel: "Staff Dashboard",
highlights: [
"Moderation & triage",
"Operational dashboards",
"Internal tools & audits",
],
staffOnly: true,
},
];
const EXPERIENCE_OPTIONS = [
{ value: "beginner", label: "Pathfinder (Beginner)" },
{ value: "intermediate", label: "Innovator (Intermediate)" },
{ value: "advanced", label: "Visionary (Advanced)" },
{ value: "expert", label: "Architect (Expert)" },
];
interface RealmSwitcherProps {
selectedRealm: RealmKey | null;
onRealmChange: (realm: RealmKey) => void;
selectedExperience: string;
onExperienceChange: (value: string) => void;
hasChanges: boolean;
onSave: () => void;
saving: boolean;
}
const RealmSwitcher = memo(function RealmSwitcher({
selectedRealm,
onRealmChange,
selectedExperience,
onExperienceChange,
hasChanges,
onSave,
saving,
}: RealmSwitcherProps) {
const { roles } = useAuth();
const canSeeStaff = useMemo(
() =>
roles.some((r) =>
["owner", "admin", "founder", "staff", "employee"].includes(
r.toLowerCase(),
),
),
[roles],
);
const visibleOptions = useMemo(
() => REALM_OPTIONS.filter((o) => (o.staffOnly ? canSeeStaff : true)),
[canSeeStaff],
);
return (
<div className="space-y-6">
<div className="space-y-2">
<h3 className="text-lg font-semibold text-foreground flex items-center gap-2">
<Rocket className="h-4 w-4 text-aethex-400" />
Realm & Path
</h3>
<p className="text-sm text-muted-foreground">
Tailor your AeThex experience. Choose the realm that matches your
goals and align the path difficulty that fits your craft.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{visibleOptions.map((realm, index) => {
const Icon = realm.icon;
const isActive = selectedRealm === realm.id;
return (
<Card
key={realm.id}
className={cn(
"relative overflow-hidden border border-border/50 transition-all duration-300 hover:border-aethex-400/50 hover:shadow-lg",
isActive && "border-aethex-400/70 shadow-xl",
)}
style={{ animationDelay: `${index * 0.05}s` }}
>
<div
className={cn(
"absolute inset-x-0 top-0 h-1 bg-gradient-to-r",
`from-transparent via-aethex-300/40 to-transparent`,
isActive ? "opacity-100" : "opacity-0",
)}
/>
<CardHeader className="space-y-3">
<div className="flex items-center gap-3">
<div
className={cn(
"flex h-12 w-12 items-center justify-center rounded-xl text-white shadow-lg",
`bg-gradient-to-br ${realm.gradient}`,
)}
>
<Icon className="h-6 w-6" />
</div>
<div>
<CardTitle className="text-base font-semibold text-foreground">
{realm.title}
</CardTitle>
<CardDescription className="text-xs text-muted-foreground">
{realm.name}
</CardDescription>
</div>
</div>
<p className="text-sm text-muted-foreground leading-relaxed">
{realm.description}
</p>
<Badge
variant="outline"
className="w-fit border-aethex-400/40 text-[11px] uppercase tracking-wider"
>
Suggested route: {realm.routeLabel}
</Badge>
</CardHeader>
<CardContent className="space-y-4">
<ul className="space-y-2 text-sm text-muted-foreground">
{realm.highlights.map((highlight, highlightIndex) => (
<li key={highlightIndex} className="flex items-start gap-2">
<Compass className="h-3.5 w-3.5 mt-0.5 text-aethex-400" />
<span>{highlight}</span>
</li>
))}
</ul>
<div className="flex flex-wrap items-center gap-2 pt-2">
<Button
type="button"
variant={isActive ? "default" : "outline"}
className={cn(
"flex items-center gap-2",
isActive
? "bg-gradient-to-r from-aethex-500 to-neon-blue"
: "hover:border-aethex-400/60",
)}
onClick={() => onRealmChange(realm.id)}
>
{isActive ? (
<Check className="h-4 w-4" />
) : (
<ArrowRight className="h-4 w-4" />
)}
{isActive ? "Realm active" : "Activate realm"}
</Button>
<Button asChild variant="ghost" size="sm" className="text-xs">
<Link to={realm.route}>Visit {realm.routeLabel}</Link>
</Button>
</div>
</CardContent>
</Card>
);
})}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<p className="text-sm font-medium text-foreground">Path difficulty</p>
<p className="text-xs text-muted-foreground">
Tune the experience level that unlocks curated resources and
challenges matched to your expertise.
</p>
</div>
<Select value={selectedExperience} onValueChange={onExperienceChange}>
<SelectTrigger>
<SelectValue placeholder="Choose your experience path" />
</SelectTrigger>
<SelectContent>
{EXPERIENCE_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs text-muted-foreground">
Your realm influences recommendations, default routes, and
collaboration invites. You can change it anytime.
</p>
<Button
type="button"
disabled={!hasChanges || saving || !selectedRealm}
onClick={onSave}
className="bg-gradient-to-r from-aethex-500 to-neon-blue hover:from-aethex-600 hover:to-neon-blue/90"
>
{saving ? "Saving..." : "Save realm & path"}
</Button>
</div>
</div>
);
});
export default RealmSwitcher;