Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 70 additions & 18 deletions scripts/ensure-admin-build.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const file = path.join(root, "src", "routes", "_authenticated", "admin.tsx");

let source = fs.readFileSync(file, "utf8");
const newline = source.includes("\r\n") ? "\r\n" : "\n";

// Keep the existing production build guard for the historical duplicate Metric.
const duplicate = `\nfunction Metric({ label, value }: { label: string; value: number }) { return <div className="glass rounded-2xl p-5"><div className="text-xs text-muted-foreground">{label}</div><div className="mt-1 font-display text-3xl font-bold gradient-text">{value}</div></div>; }`;

const count = source.split(duplicate).length - 1;

if (count === 2) {
Expand All @@ -21,34 +25,82 @@ if (count === 2) {
throw new Error(`Unexpected Metric declaration count: ${count}`);
}

// The Admin Control Center remains in the existing route file. This idempotent
// preparation step wires the Devices panel into that route for both dev and build.
const devicesImport = 'import { DevicesPanel } from "@/components/DevicesPanel";';
// The Admin Control Center remains in the existing route file.
// This idempotent preparation step wires the Devices panel into that route
// for both development and production builds.

const devicesImport =
'import { DevicesPanel } from "@/components/DevicesPanel";';

if (!source.includes(devicesImport)) {
const anchor = 'import { SecurityTab } from "@/components/SecurityTab";';
if (!source.includes(anchor)) throw new Error("Admin SecurityTab import anchor not found");
source = source.replace(anchor, `${anchor}\n${devicesImport}`);
const anchor =
'import { SecurityTab } from "@/components/SecurityTab";';

if (!source.includes(anchor)) {
throw new Error("Admin SecurityTab import anchor not found");
}

source = source.replace(
anchor,
`${anchor}${newline}${devicesImport}`
);
}

// Add the Devices navigation item.
const devicesNav = '["devices", "Devices", Monitor],';

if (!source.includes(devicesNav)) {
const anchor = '["export", "Export", Download], ["trash", "Trash / Recovery", Archive], ["profile", "Admin Profile", Users],';
if (!source.includes(anchor)) throw new Error("Admin navigation anchor not found");
source = source.replace(anchor, `${anchor}\n ${devicesNav}`);
const anchor =
'["export", "Export", Download], ["trash", "Trash / Recovery", Archive], ["profile", "Admin Profile", Users],';

if (!source.includes(anchor)) {
throw new Error("Admin navigation anchor not found");
}

source = source.replace(
anchor,
`${anchor}${newline} ${devicesNav}`
);
}

if (source.includes(devicesNav) && !source.includes(" Monitor,")) {
const anchor = " MessageCircle,\n";
if (!source.includes(anchor)) throw new Error("Admin lucide import anchor not found");
source = source.replace(anchor, `${anchor} Monitor,\n`);
// Ensure Monitor is imported from lucide-react.
// Match the MessageCircle import regardless of line-ending format.
if (
source.includes(devicesNav) &&
!/^[ \t]*Monitor,[ \t]*$/m.test(source)
) {
const anchor = /^([ \t]*)MessageCircle,[ \t]*$/m;

if (!anchor.test(source)) {
throw new Error("Admin lucide import anchor not found");
}

source = source.replace(
anchor,
(line, indentation) =>
`${line}${newline}${indentation}Monitor,`
);
}

const devicesRender = '{tab === "devices" && <DevicesPanel />}';
// Render the Devices panel when its navigation tab is selected.
const devicesRender =
'{tab === "devices" && <DevicesPanel />}';

if (!source.includes(devicesRender)) {
const anchor = '{tab === "profile" && <ProfilePanel userId={user?.id ?? ""} />}';
if (!source.includes(anchor)) throw new Error("Admin panel render anchor not found");
source = source.replace(anchor, `${anchor}\n ${devicesRender}`);
const anchor =
'{tab === "profile" && <ProfilePanel userId={user?.id ?? ""} />}';

if (!source.includes(anchor)) {
throw new Error("Admin panel render anchor not found");
}

source = source.replace(
anchor,
`${anchor}${newline} ${devicesRender}`
);
}

// Write the updated Admin route.
fs.writeFileSync(file, source, "utf8");
console.log("Admin Devices integration: ready");

console.log("Admin Devices integration: ready");
21 changes: 21 additions & 0 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as ThankYouRouteImport } from './routes/thank-you'
import { Route as TermsRouteImport } from './routes/terms'
import { Route as ResumeAccessRouteImport } from './routes/resume-access'
import { Route as ResetPasswordRouteImport } from './routes/reset-password'
import { Route as ProjectsRouteImport } from './routes/projects'
import { Route as PrivacyPolicyRouteImport } from './routes/privacy-policy'
Expand Down Expand Up @@ -39,6 +40,11 @@ const TermsRoute = TermsRouteImport.update({
path: '/terms',
getParentRoute: () => rootRouteImport,
} as any)
const ResumeAccessRoute = ResumeAccessRouteImport.update({
id: '/resume-access',
path: '/resume-access',
getParentRoute: () => rootRouteImport,
} as any)
const ResetPasswordRoute = ResetPasswordRouteImport.update({
id: '/reset-password',
path: '/reset-password',
Expand Down Expand Up @@ -138,6 +144,7 @@ export interface FileRoutesByFullPath {
'/privacy-policy': typeof PrivacyPolicyRoute
'/projects': typeof ProjectsRoute
'/reset-password': typeof ResetPasswordRoute
'/resume-access': typeof ResumeAccessRoute
'/terms': typeof TermsRoute
'/thank-you': typeof ThankYouRoute
'/admin': typeof AuthenticatedAdminRoute
Expand All @@ -158,6 +165,7 @@ export interface FileRoutesByTo {
'/privacy-policy': typeof PrivacyPolicyRoute
'/projects': typeof ProjectsRoute
'/reset-password': typeof ResetPasswordRoute
'/resume-access': typeof ResumeAccessRoute
'/terms': typeof TermsRoute
'/thank-you': typeof ThankYouRoute
'/admin': typeof AuthenticatedAdminRoute
Expand All @@ -180,6 +188,7 @@ export interface FileRoutesById {
'/privacy-policy': typeof PrivacyPolicyRoute
'/projects': typeof ProjectsRoute
'/reset-password': typeof ResetPasswordRoute
'/resume-access': typeof ResumeAccessRoute
'/terms': typeof TermsRoute
'/thank-you': typeof ThankYouRoute
'/_authenticated/admin': typeof AuthenticatedAdminRoute
Expand All @@ -202,6 +211,7 @@ export interface FileRouteTypes {
| '/privacy-policy'
| '/projects'
| '/reset-password'
| '/resume-access'
| '/terms'
| '/thank-you'
| '/admin'
Expand All @@ -222,6 +232,7 @@ export interface FileRouteTypes {
| '/privacy-policy'
| '/projects'
| '/reset-password'
| '/resume-access'
| '/terms'
| '/thank-you'
| '/admin'
Expand All @@ -243,6 +254,7 @@ export interface FileRouteTypes {
| '/privacy-policy'
| '/projects'
| '/reset-password'
| '/resume-access'
| '/terms'
| '/thank-you'
| '/_authenticated/admin'
Expand All @@ -265,6 +277,7 @@ export interface RootRouteChildren {
PrivacyPolicyRoute: typeof PrivacyPolicyRoute
ProjectsRoute: typeof ProjectsRoute
ResetPasswordRoute: typeof ResetPasswordRoute
ResumeAccessRoute: typeof ResumeAccessRoute
TermsRoute: typeof TermsRoute
ThankYouRoute: typeof ThankYouRoute
BlogsSlugRoute: typeof BlogsSlugRoute
Expand All @@ -286,6 +299,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TermsRouteImport
parentRoute: typeof rootRouteImport
}
'/resume-access': {
id: '/resume-access'
path: '/resume-access'
fullPath: '/resume-access'
preLoaderRoute: typeof ResumeAccessRouteImport
parentRoute: typeof rootRouteImport
}
'/reset-password': {
id: '/reset-password'
path: '/reset-password'
Expand Down Expand Up @@ -450,6 +470,7 @@ const rootRouteChildren: RootRouteChildren = {
PrivacyPolicyRoute: PrivacyPolicyRoute,
ProjectsRoute: ProjectsRoute,
ResetPasswordRoute: ResetPasswordRoute,
ResumeAccessRoute: ResumeAccessRoute,
TermsRoute: TermsRoute,
ThankYouRoute: ThankYouRoute,
BlogsSlugRoute: BlogsSlugRoute,
Expand Down
4 changes: 4 additions & 0 deletions src/routes/_authenticated/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
LayoutDashboard,
Mail,
MessageCircle,
Monitor,
Paperclip,
Pencil,
Plus,
Expand All @@ -41,6 +42,7 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { PostEditor, type PostDraft } from "@/components/PostEditor";
import { SecurityTab } from "@/components/SecurityTab";
import { DevicesPanel } from "@/components/DevicesPanel";
import { getAdminAuditEntries, recordAdminAudit } from "@/lib/admin-audit";
import { useAuth } from "@/hooks/use-auth";
import { toast } from "sonner";
Expand Down Expand Up @@ -89,6 +91,7 @@ const navItems = [
["security", "Security", ShieldCheck], ["activity", "Audit / Activity", Activity], ["analytics", "Analytics", BarChart3], ["comments", "Comments", MessageCircle],
["engagement", "Engagement", ClipboardList], ["bookmarks", "Bookmarks", BookOpen], ["search", "Global Search", Search], ["health", "System Health", RefreshCw],
["export", "Export", Download], ["trash", "Trash / Recovery", Archive], ["profile", "Admin Profile", Users],
["devices", "Devices", Monitor],
] as const;
type AdminTab = (typeof navItems)[number][0];

Expand Down Expand Up @@ -133,6 +136,7 @@ function Admin() {
{tab === "export" && <ExportPanel posts={posts.data ?? []} projects={projects.data ?? []} messages={messages.data ?? []} />}
{tab === "trash" && <TrashPanel messages={messages.data ?? []} refetch={messages.refetch} onActivity={recordActivity} />}
{tab === "profile" && <ProfilePanel userId={user?.id ?? ""} />}
{tab === "devices" && <DevicesPanel />}
</div>
</section></SiteLayout>;
}
Expand Down
Loading